mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-15 03:41:28 +00:00
Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cf16febf6 | ||
|
|
70e52a105f | ||
|
|
b525cb79e1 | ||
|
|
86ab598899 | ||
|
|
ea90f45127 | ||
|
|
c7b4e8ef84 | ||
|
|
8f1103f064 | ||
|
|
6e42a9b0ae | ||
|
|
e77ba5f672 | ||
|
|
46b3871f8d | ||
|
|
84768da6a1 | ||
|
|
bb0d8ee1c4 | ||
|
|
f32591ed64 | ||
|
|
db9917e722 | ||
|
|
cb752d38a3 | ||
|
|
5bce937d07 | ||
|
|
de3af0b7de | ||
|
|
5a427f2eb6 |
@@ -570,6 +570,11 @@ 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
|
- 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)`
|
sub, it builds a `wxMouseEvent(wxEVT_MOTION)` from `wxGetMousePosition()`, calls `mainDropDown->mouseMove(ev)`
|
||||||
**directly** (nothing is posted), and calls `e.RequestMore()` to keep polling.
|
**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
|
- **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",
|
`DoSetSize` only uses `gtk_widget_set_size_request` ("gtk_window_resize does not work for GTK_WINDOW_POPUP",
|
||||||
`src/gtk/popupwin.cpp:181-188`).
|
`src/gtk/popupwin.cpp:181-188`).
|
||||||
@@ -607,6 +612,32 @@ Details [source] (`Widgets/PopupWindow.cpp`).
|
|||||||
```
|
```
|
||||||
Cite: c0ae2bda99, b9ff15054f (`DropDown::mouseMove`, `DropDown::messureSize`, `DropDown::ShouldDismissOnTopWindowDeactivate`,
|
Cite: c0ae2bda99, b9ff15054f (`DropDown::mouseMove`, `DropDown::messureSize`, `DropDown::ShouldDismissOnTopWindowDeactivate`,
|
||||||
`PopupWindow::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
|
- **Rule:** When shrinking a dropdown to fit above the display bottom, give up if fewer than two rows would
|
||||||
remain.
|
remain.
|
||||||
**Why:** On Wayland the popup can land where almost no vertical space is left. Clamping to that sliver made an
|
**Why:** On Wayland the popup can land where almost no vertical space is left. Clamping to that sliver made an
|
||||||
|
|||||||
+22
-3
@@ -8,7 +8,7 @@ SCRIPT_PATH=$(dirname "$(readlink -f "${0}")")
|
|||||||
pushd "${SCRIPT_PATH}" > /dev/null
|
pushd "${SCRIPT_PATH}" > /dev/null
|
||||||
|
|
||||||
function usage() {
|
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]"
|
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 " -1: limit builds to one core (where possible)"
|
echo " -1: limit builds to one core (where possible)"
|
||||||
echo " -j N: limit builds to N cores (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)"
|
echo " -J N: build up to N dependencies at a time, each still using -j jobs (default: 1)"
|
||||||
@@ -29,11 +29,15 @@ function usage() {
|
|||||||
echo " -u: install system dependencies (asks for sudo password; build prerequisite)"
|
echo " -u: install system dependencies (asks for sudo password; build prerequisite)"
|
||||||
echo " -l: use Clang instead of GCC (default: GCC)"
|
echo " -l: use Clang instead of GCC (default: GCC)"
|
||||||
echo " -L: use ld.lld as linker (if available)"
|
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 "For a first use, you want to './${SCRIPT_NAME} -u'"
|
||||||
echo " and then './${SCRIPT_NAME} -dsi'"
|
echo " and then './${SCRIPT_NAME} -dsi'"
|
||||||
echo "For a GitHub Actions-like Linux build locally, use './${SCRIPT_NAME} -g -istrlL'"
|
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 "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 "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"
|
SLIC3R_PRECOMPILED_HEADERS="ON"
|
||||||
@@ -131,6 +135,20 @@ while getopts ":1j:J:bcCdDeFghiprstulL" opt ; do
|
|||||||
esac
|
esac
|
||||||
done
|
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
|
if [ ${OPTIND} -eq 1 ] ; then
|
||||||
usage
|
usage
|
||||||
exit 1
|
exit 1
|
||||||
@@ -547,7 +565,7 @@ if [[ -n "${BUILD_DEPS}" ]] ; then
|
|||||||
BUILD_ARGS+=(-DCMAKE_BUILD_TYPE="${BUILD_CONFIG}")
|
BUILD_ARGS+=(-DCMAKE_BUILD_TYPE="${BUILD_CONFIG}")
|
||||||
fi
|
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[@]}"
|
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[@]}"
|
||||||
# The top-level build runs one dependency at a time by default, which keeps the console
|
# 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.
|
# 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
|
# -J raises the top level instead, and -j still applies in full to every dependency, so the
|
||||||
@@ -593,7 +611,8 @@ if [[ -n "${BUILD_ORCA}" ]] || [[ -n "${BUILD_TESTS}" ]] ; then
|
|||||||
-DSLIC3R_PCH=${SLIC3R_PRECOMPILED_HEADERS} \
|
-DSLIC3R_PCH=${SLIC3R_PRECOMPILED_HEADERS} \
|
||||||
-DORCA_TOOLS=ON \
|
-DORCA_TOOLS=ON \
|
||||||
"${COLORED_OUTPUT}" \
|
"${COLORED_OUTPUT}" \
|
||||||
"${BUILD_ARGS[@]}"
|
"${BUILD_ARGS[@]}" \
|
||||||
|
"${EXTRA_CMAKE_ARGS[@]}"
|
||||||
echo "done"
|
echo "done"
|
||||||
if [[ -n "${BUILD_ORCA}" ]]; then
|
if [[ -n "${BUILD_ORCA}" ]]; then
|
||||||
echo "Building OrcaSlicer ..."
|
echo "Building OrcaSlicer ..."
|
||||||
|
|||||||
+38
-22
@@ -82,9 +82,7 @@ there.
|
|||||||
strong point is placed, no later strong modifier and no weak modifier is
|
strong point is placed, no later strong modifier and no weak modifier is
|
||||||
processed for that perimeter.
|
processed for that perimeter.
|
||||||
- **Weak:** every weak modifier applies. They are applied from the lowest
|
- **Weak:** every weak modifier applies. They are applied from the lowest
|
||||||
priority to the highest, so the highest one overwrites overlapping zones. A
|
priority to the highest, so the highest one overwrites overlapping zones.
|
||||||
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
|
A strong modifier without a usable segment, even one whose fragments were all
|
||||||
discarded, passes the turn to the next one.
|
discarded, passes the turn to the next one.
|
||||||
@@ -232,20 +230,23 @@ begin and end positions on the source contour.
|
|||||||
### Full containment
|
### Full containment
|
||||||
|
|
||||||
A modifier that covers the whole perimeter has no boundaries on it. The policy
|
A modifier that covers the whole perimeter has no boundaries on it. The policy
|
||||||
follows seam painting, where painting a whole perimeter green is a meaningful
|
follows seam painting, where a whole perimeter can be painted in either color:
|
||||||
choice and forbidding the seam all round is not:
|
|
||||||
|
|
||||||
- **Seam Enforced** types the whole perimeter, like a perimeter painted green all
|
- **Seam Enforced** types the whole perimeter, like a perimeter painted green all
|
||||||
round, with subdivision applied as described under [Weak modifiers](#weak-modifiers).
|
round, with subdivision applied as described under [Weak modifiers](#weak-modifiers).
|
||||||
- **Seam Neutral** types the whole perimeter Neutral, like an unmarked perimeter,
|
- **Seam Neutral** types the whole perimeter Neutral, like an unmarked perimeter,
|
||||||
clearing painting and lower zones.
|
clearing painting and lower zones.
|
||||||
- **Seam Blocked** is skipped for the perimeter, with the full-containment
|
- **Seam Blocked** types the whole perimeter Blocked, like a perimeter painted
|
||||||
warning. The seam cannot avoid the whole perimeter, so the modifier does not
|
red all round, overriding painting and lower zones. The seam cannot avoid such
|
||||||
override anything below it: lower zones and painting stay in effect.
|
a perimeter: the seam placer compares Blocked candidates with each other like
|
||||||
- **Seam Center, Left and Right** are skipped with the same warning: there is no
|
Neutral ones, so the seam is placed as on an unmarked perimeter. If the
|
||||||
intersection to place the point on.
|
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.
|
||||||
|
|
||||||
Enforced and Neutral take part in the usual priority order (see
|
Enforced, Neutral and Blocked take part in the usual priority order (see
|
||||||
[Weak modifiers](#weak-modifiers)).
|
[Weak modifiers](#weak-modifiers)).
|
||||||
|
|
||||||
The perimeter is fully contained when the united intervals cover every source
|
The perimeter is fully contained when the united intervals cover every source
|
||||||
@@ -311,9 +312,9 @@ into every perimeter that has a strong seam.
|
|||||||
`collect_weak_modifier_segments()` extracts the segments of every weak modifier
|
`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
|
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
|
segment becomes a zone with a type and two boundaries, kept in application
|
||||||
order, lowest priority first. Full containment of an Enforced or Neutral
|
order, lowest priority first. Full containment of a weak modifier becomes a
|
||||||
modifier becomes a whole-perimeter zone at its place in that order: it has no
|
whole-perimeter zone at its place in that order: it has no boundaries and takes
|
||||||
boundaries and takes part in no insertion or helper step below. The boundaries
|
part in no insertion or helper step below. The boundaries
|
||||||
carry their positions on the source contour; these remain as provenance after
|
carry their positions on the source contour; these remain as provenance after
|
||||||
insertion and are not indices into the modified polygon.
|
insertion and are not indices into the modified polygon.
|
||||||
|
|
||||||
@@ -384,9 +385,10 @@ G-code export issues it as one non-critical warning with the ID
|
|||||||
`SlicingPreciseSeamWarning`. It is a single line, "Precise Seam: <causes>. Seam
|
`SlicingPreciseSeamWarning`. It is a single line, "Precise Seam: <causes>. Seam
|
||||||
placement may differ from expected.", because the export warnings dialog shows
|
placement may differ from expected.", because the export warnings dialog shows
|
||||||
only the first line of each warning. Repeated warning events replace the
|
only the first line of each warning. Repeated warning events replace the
|
||||||
notification instead of appending to it. Except for the "had no effect" cause,
|
notification instead of appending to it. Except for the "covers an entire
|
||||||
the causes name the modifier types involved, as the menu names them, in menu
|
perimeter" and "had no effect" causes, the causes name the modifier types
|
||||||
order and each type once, for example "(Seam Left, Seam Enforced)".
|
involved, as the menu names them, in menu order and each type once, for example
|
||||||
|
"(Seam Left, Seam Enforced)".
|
||||||
The causes are:
|
The causes are:
|
||||||
|
|
||||||
- **failed to process some intersections (types):** at least one fragment was
|
- **failed to process some intersections (types):** at least one fragment was
|
||||||
@@ -395,8 +397,18 @@ The causes are:
|
|||||||
a Seam Center, Left or Right modifier had more than one segment on a
|
a Seam Center, Left or Right modifier had more than one segment on a
|
||||||
perimeter (see [Strong modifiers](#strong-modifiers)).
|
perimeter (see [Strong modifiers](#strong-modifiers)).
|
||||||
- **a perimeter is fully inside a modifier, the modifier was not applied to it
|
- **a perimeter is fully inside a modifier, the modifier was not applied to it
|
||||||
(types):** a Seam Center, Left, Right or Blocked modifier was skipped for a
|
(types):** a Seam Center, Left or Right modifier was skipped for a
|
||||||
perimeter (see [Full containment](#full-containment)).
|
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
|
- **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
|
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
|
least one perimeter and never gave a segment, full containment or a discarded
|
||||||
@@ -420,6 +432,8 @@ The log records the following diagnostic markers:
|
|||||||
- `[PreciseSeamNoEffect]` for every modifier of the "had no effect" cause, with
|
- `[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
|
the object and modifier names. Unlike the user warning, the log lists all of
|
||||||
them.
|
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
|
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
|
`init()` call are logged in detail, in parallel processing order; if a limit is
|
||||||
@@ -563,12 +577,14 @@ last modifier would.
|
|||||||
priorities, weak boundaries that coincide or share an edge, enforced
|
priorities, weak boundaries that coincide or share an edge, enforced
|
||||||
subdivision, whole-perimeter weak zones with painting and priorities, weak
|
subdivision, whole-perimeter weak zones with painting and priorities, weak
|
||||||
zones over painting's oversampled candidates, the warning type masks, usage
|
zones over painting's oversampled candidates, the warning type masks, usage
|
||||||
tracking for the "had no effect" warning, volume sorting of strong and weak
|
tracking for the "had no effect" and whole-perimeter Blocked warnings, volume
|
||||||
groups, restoration of strong points after alignment, raft layer indexing and
|
sorting of strong and weak groups, restoration of strong points after
|
||||||
structured slices. End-to-end tests slice a real object with Precise Seam
|
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
|
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,
|
mode under several seam positions and with a raft, Enforced and Blocked zones,
|
||||||
a modifier with a hole, and the user warning.
|
a perimeter blocked all round under every seam position, a modifier with a
|
||||||
|
hole, and the user warning.
|
||||||
- [Seam placer tests](../../tests/fff_print/test_seam_placer.cpp) cover
|
- [Seam placer tests](../../tests/fff_print/test_seam_placer.cpp) cover
|
||||||
enforced-patch selection independent of the contour start, fully painted
|
enforced-patch selection independent of the contour start, fully painted
|
||||||
contours, duplicate removal, and `Print::apply()` synchronization through type
|
contours, duplicate removal, and `Print::apply()` synchronization through type
|
||||||
|
|||||||
@@ -12665,6 +12665,16 @@ msgstr ""
|
|||||||
msgid "a perimeter is fully inside a modifier, the modifier was not applied to it (%1%)"
|
msgid "a perimeter is fully inside a modifier, the modifier was not applied to it (%1%)"
|
||||||
msgstr ""
|
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
|
#, possible-boost-format
|
||||||
msgid "modifier \"%1%\" of \"%2%\" had no effect on the seam (it might not reach the centerline of the printed perimeter)"
|
msgid "modifier \"%1%\" of \"%2%\" had no effect on the seam (it might not reach the centerline of the printed perimeter)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"name": "Goofoo",
|
||||||
|
"version": "02.04.00.01",
|
||||||
|
"force_update": "0",
|
||||||
|
"description": "Goofoo configurations",
|
||||||
|
"machine_model_list": [
|
||||||
|
{
|
||||||
|
"name": "Goofoo GP1",
|
||||||
|
"sub_path": "machine/Goofoo GP1.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo TurboM",
|
||||||
|
"sub_path": "machine/Goofoo TurboM.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo Velo+",
|
||||||
|
"sub_path": "machine/Goofoo Velo+.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo Velo+ Tool",
|
||||||
|
"sub_path": "machine/Goofoo Velo+ Tool.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"process_list": [
|
||||||
|
{
|
||||||
|
"name": "fdm_process_common",
|
||||||
|
"sub_path": "process/fdm_process_common.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fdm_process_klipper_common",
|
||||||
|
"sub_path": "process/fdm_process_klipper_common.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "0.20mm Standard @Goofoo GP1",
|
||||||
|
"sub_path": "process/0.20mm Standard @Goofoo GP1.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "0.20mm Standard @Goofoo TurboM",
|
||||||
|
"sub_path": "process/0.20mm Standard @Goofoo TurboM.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "0.20mm Standard @Goofoo Velo+",
|
||||||
|
"sub_path": "process/0.20mm Standard @Goofoo Velo+.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "0.30mm Standard @Goofoo Velo+",
|
||||||
|
"sub_path": "process/0.30mm Standard @Goofoo Velo+.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "0.10mm Standard @Goofoo Velo+",
|
||||||
|
"sub_path": "process/0.10mm Standard @Goofoo Velo+.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filament_list": [],
|
||||||
|
"machine_list": [
|
||||||
|
{
|
||||||
|
"name": "fdm_machine_common",
|
||||||
|
"sub_path": "machine/fdm_machine_common.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fdm_klipper_common",
|
||||||
|
"sub_path": "machine/fdm_klipper_common.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo GP1 0.4 nozzle",
|
||||||
|
"sub_path": "machine/Goofoo GP1 0.4 nozzle.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo TurboM 0.4 nozzle",
|
||||||
|
"sub_path": "machine/Goofoo TurboM 0.4 nozzle.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo Velo+ 0.2 nozzle",
|
||||||
|
"sub_path": "machine/Goofoo Velo+ 0.2 nozzle.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo Velo+ 0.4 nozzle",
|
||||||
|
"sub_path": "machine/Goofoo Velo+ 0.4 nozzle.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo Velo+ 0.6 nozzle",
|
||||||
|
"sub_path": "machine/Goofoo Velo+ 0.6 nozzle.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo Velo+ Tool 0.4",
|
||||||
|
"sub_path": "machine/Goofoo Velo+ Tool 0.4.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
|
||||||
|
<g id="Layer_1">
|
||||||
|
<title>Layer 1</title>
|
||||||
|
<text fill="#0000ff" x="31.38885" y="400.22266" id="svg_1" font-size="24" font-family="Noto Sans JP" text-anchor="start" xml:space="preserve" stroke="#0000ff" transform="matrix(2.19884 0 0 1.66297 -63.896 -416.238)">GP1</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 373 B |
Binary file not shown.
@@ -0,0 +1,7 @@
|
|||||||
|
<svg width="600" height="480" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|
||||||
|
<g>
|
||||||
|
<title>Layer 1</title>
|
||||||
|
<text transform="matrix(2.3401 0 0 1.462 -92.4668 -168.496)" stroke="#000" stroke-width="3" xml:space="preserve" text-anchor="start" font-family="Noto Sans JP" font-size="24" id="svg_1" y="421.25588" x="52.33404" fill="#000000">TurboM</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 353 B |
Binary file not shown.
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="480" viewBox="0 0 600 480">
|
||||||
|
<g id="Layer_1">
|
||||||
|
<title>Layer 1</title>
|
||||||
|
<text transform="matrix(2.19884 0 0 1.66297 -203.896 -238.238)" stroke="#0000ff" xml:space="preserve" text-anchor="start" font-family="Noto Sans JP" font-size="24" id="svg_1" y="400.22266" x="120.07198" fill="#0000ff">VELO+</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 376 B |
Binary file not shown.
@@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
"type": "machine",
|
||||||
|
"name": "Goofoo GP1 0.4 nozzle",
|
||||||
|
"inherits": "fdm_klipper_common",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "x4GJ08PooNDQ8WJj",
|
||||||
|
"instantiation": "true",
|
||||||
|
"nozzle_diameter": [
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"default_filament_profile": [
|
||||||
|
"Goofoo ABS @System"
|
||||||
|
],
|
||||||
|
"printer_model": "Goofoo GP1",
|
||||||
|
"default_print_profile": "0.20mm Standard @Goofoo GP1",
|
||||||
|
"printer_variant": "0.4",
|
||||||
|
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\n;G92 E0",
|
||||||
|
"change_filament_gcode": "G5",
|
||||||
|
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\n;layer\n;{if layer_num == 2}\n;M106 P1 S255\n;{endif}\nSET_PRINT_STATS_INFO CURRENT_LAYER=[layer_num]\n",
|
||||||
|
"machine_end_gcode": "; End G-code\nEND_PRINT",
|
||||||
|
"machine_pause_gcode": "G5",
|
||||||
|
"machine_start_gcode": "M117 Printing...\nM107 ; Turn off the fan\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nM104 S140\nM190 S[first_layer_bed_temperature] ; wait for bed temp\n;G32 ;Load bed mesh\nG28 ;home\nG29 bed leveling\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nG0 Z2.0 F600;\nG0 X50 Y10 F8000;\nG92 E0.0 ; reset extruder distance position\nG0 Z0.4 F600;\nG1 X100.0 E10 F3000.0 ; intro line\nG92 E0.0 ; reset extruder distance position\nG1 X200.0 E15 F3000.0 ; intro line\nG92 E0.0 ; reset extruder distance position\nG0 Z0.8 F600;\nG1 X100.0 E15 F3000.0 ; intro line\nG1 Z0.4 F600 ;Wipe\nG0 Y12 F6000 ;Wipe\nG1 X100 F6000 ;Wipe\nG0 Y8 F12000 ;Wipe\nG1 X200 F6000 ;Wipe\nG1 X190 Y12 F6000 ;Wipe\nG1 X180 Y8 F6000 ;Wipe\nG1 X170 Y12 F6000 ;Wipe\nG1 X160 Y8 F6000 ;Wipe\nG1 X150 Y12 F6000 ;Wipe\n;G0 Z2.0 F600;\nG92 E0.0 ; reset extruder distance position\n;START_PRINT",
|
||||||
|
"purge_in_prime_tower": "0",
|
||||||
|
"time_lapse_gcode": ";TIMELAPSE_TAKE_FRAME",
|
||||||
|
"printable_height": "250",
|
||||||
|
"printable_area": [
|
||||||
|
"0x0",
|
||||||
|
"250x0",
|
||||||
|
"250x250",
|
||||||
|
"0x250"
|
||||||
|
],
|
||||||
|
"deretraction_speed": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"extruder_clearance_height_to_lid": "140",
|
||||||
|
"extruder_clearance_height_to_rod": "36",
|
||||||
|
"extruder_clearance_radius": "65",
|
||||||
|
"machine_max_acceleration_e": [
|
||||||
|
"20000",
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_retracting": [
|
||||||
|
"15000",
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_z": [
|
||||||
|
"100",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_e": [
|
||||||
|
"1",
|
||||||
|
"2.5"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_x": [
|
||||||
|
"5",
|
||||||
|
"9"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_y": [
|
||||||
|
"5",
|
||||||
|
"9"
|
||||||
|
],
|
||||||
|
"machine_max_speed_e": [
|
||||||
|
"25",
|
||||||
|
"25"
|
||||||
|
],
|
||||||
|
"machine_max_speed_x": [
|
||||||
|
"500",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_speed_y": [
|
||||||
|
"500",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_speed_z": [
|
||||||
|
"12",
|
||||||
|
"12"
|
||||||
|
],
|
||||||
|
"retract_before_wipe": [
|
||||||
|
"100%"
|
||||||
|
],
|
||||||
|
"retract_length_toolchange": [
|
||||||
|
"10"
|
||||||
|
],
|
||||||
|
"retraction_length": [
|
||||||
|
"0.5"
|
||||||
|
],
|
||||||
|
"retraction_minimum_travel": [
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"retraction_speed": [
|
||||||
|
"50"
|
||||||
|
],
|
||||||
|
"thumbnails": "300x300/PNG",
|
||||||
|
"z_hop": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"upward_compatible_machine": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"type": "machine_model",
|
||||||
|
"name": "Goofoo GP1",
|
||||||
|
"nozzle_diameter": "0.4",
|
||||||
|
"bed_model": "Goofoo-GP1.stl",
|
||||||
|
"bed_texture": "Goofoo-GP1-texture.svg",
|
||||||
|
"family": "Goofoo",
|
||||||
|
"machine_tech": "FFF",
|
||||||
|
"model_id": "V003",
|
||||||
|
"default_materials": "Goofoo ABS @System"
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"type": "machine",
|
||||||
|
"name": "Goofoo TurboM 0.4 nozzle",
|
||||||
|
"inherits": "fdm_klipper_common",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "Wmdn5zTy1HH93Q5d",
|
||||||
|
"instantiation": "true",
|
||||||
|
"nozzle_diameter": [
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"nozzle_type": "brass",
|
||||||
|
"printer_model": "Goofoo TurboM",
|
||||||
|
"printer_variant": "0.4",
|
||||||
|
"default_filament_profile": [
|
||||||
|
"Goofoo ABS @System"
|
||||||
|
],
|
||||||
|
"default_print_profile": "0.20mm Standard @Goofoo TurboM",
|
||||||
|
"printable_area": [
|
||||||
|
"0x0",
|
||||||
|
"230x0",
|
||||||
|
"230x200",
|
||||||
|
"0x200"
|
||||||
|
],
|
||||||
|
"extruder_clearance_height_to_lid": "350",
|
||||||
|
"extruder_clearance_height_to_rod": "60",
|
||||||
|
"extruder_clearance_radius": "50",
|
||||||
|
"machine_end_gcode": "M104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG92 E0.0 ; reset extruder distance position;\nG1 E-1 F2100 ; retract\nG28 X Y; home X Y axis\n;M84 ; disable motors\nM107 ; turn off fan\n;SET_VELOCITY_LIMIT ACCEL_TO_DECEL=4000",
|
||||||
|
"machine_max_jerk_z": [
|
||||||
|
"3",
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"machine_start_gcode": "M107 ; Turn off the fan\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\n\n\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\n\nG28 ;home\n\nG1 X2 Y10 F3000;\n\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\n\nG1 Z0.28 F240\n\nG92 E0\n\nG1 Y140 E10 F1500 ; prime the nozzle\n\nG1 X2.3 F5000\nG92 E0\n\nG1 Y10 E10 F1200 ; prime the nozzle\n;G0 Z2.0 F600;\nG92 E0.0 ; reset extruder distance position;\nM82\n;SET_PRESSURE_ADVANCE ADVANCE=0.05\n;SET_VELOCITY_LIMIT ACCEL_TO_DECEL=10000\n",
|
||||||
|
"printable_height": "200",
|
||||||
|
"z_hop": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"machine_max_speed_e": [
|
||||||
|
"25",
|
||||||
|
"25"
|
||||||
|
],
|
||||||
|
"machine_max_speed_x": [
|
||||||
|
"500",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_speed_y": [
|
||||||
|
"500",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_speed_z": [
|
||||||
|
"12",
|
||||||
|
"12"
|
||||||
|
],
|
||||||
|
"upward_compatible_machine": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"type": "machine_model",
|
||||||
|
"name": "Goofoo TurboM",
|
||||||
|
"nozzle_diameter": "0.4",
|
||||||
|
"bed_model": "Goofoo-TurboM.stl",
|
||||||
|
"bed_texture": "Goofoo-TurboM-texture.svg",
|
||||||
|
"family": "Goofoo",
|
||||||
|
"machine_tech": "FFF",
|
||||||
|
"model_id": "V002",
|
||||||
|
"default_materials": "Goofoo ABS @System"
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
{
|
||||||
|
"type": "machine",
|
||||||
|
"name": "Goofoo Velo+ 0.2 nozzle",
|
||||||
|
"inherits": "fdm_klipper_common",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "wSr6VNECZHekeNRi",
|
||||||
|
"instantiation": "true",
|
||||||
|
"nozzle_diameter": [
|
||||||
|
"0.2"
|
||||||
|
],
|
||||||
|
"nozzle_type": "hardened_steel",
|
||||||
|
"printer_model": "Goofoo Velo+",
|
||||||
|
"printer_variant": "0.2",
|
||||||
|
"default_filament_profile": [
|
||||||
|
"Goofoo ABS @System"
|
||||||
|
],
|
||||||
|
"default_print_profile": "0.10mm Standard @Goofoo Velo+",
|
||||||
|
"printable_area": [
|
||||||
|
"0x0",
|
||||||
|
"600x0",
|
||||||
|
"600x500",
|
||||||
|
"0x500"
|
||||||
|
],
|
||||||
|
"machine_end_gcode": "{if max_layer_z < printable_height}G1 Z{z_offset+min(max_layer_z+2, printable_height)} F600 ; Move print head up{endif}\nG1 X5 Y{print_bed_max[1]*0.8} F{travel_speed*60} ; present print\n{if max_layer_z < printable_height-10}G1 Z{z_offset+min(max_layer_z+70, printable_height-10)} F600 ; Move print head further up{endif}\n{if max_layer_z < max_print_height*0.6}G1 Z{printable_height*0.6} F600 ; Move print head further up{endif}\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107 ; turn off fan\nM84 X Y E ; disable motors",
|
||||||
|
"printable_height": "600",
|
||||||
|
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0",
|
||||||
|
"change_filament_gcode": "PAUSE",
|
||||||
|
"deretraction_speed": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"extruder_clearance_height_to_lid": "350",
|
||||||
|
"extruder_clearance_height_to_rod": "60",
|
||||||
|
"extruder_clearance_radius": "50",
|
||||||
|
"machine_max_acceleration_e": [
|
||||||
|
"20000",
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_retracting": [
|
||||||
|
"15000",
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_z": [
|
||||||
|
"100",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_e": [
|
||||||
|
"1",
|
||||||
|
"2.5"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_x": [
|
||||||
|
"5",
|
||||||
|
"9"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_y": [
|
||||||
|
"5",
|
||||||
|
"9"
|
||||||
|
],
|
||||||
|
"machine_max_speed_e": [
|
||||||
|
"50",
|
||||||
|
"25"
|
||||||
|
],
|
||||||
|
"machine_max_speed_x": [
|
||||||
|
"730",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_speed_y": [
|
||||||
|
"730",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_speed_z": [
|
||||||
|
"15",
|
||||||
|
"12"
|
||||||
|
],
|
||||||
|
"machine_start_gcode": "G90 ; 绝对坐标模式\nM83 ; 挤出机相对模式\nM140 S[first_layer_bed_temperature] ; 设置热床温度\nM104 S140 ; 临时喷嘴温度=切片首层设定(防止归零渗料)\nG4 S10 ; 预热等待10秒\nG28 ; 归零所有轴\nG1 Z50 F240 ; Z轴抬升\nG1 X2 Y10 F3000 ; 移动至起始位置\nM104 S[nozzle_temperature_initial_layer] ; 正式设置喷嘴温度(与切片设置一致)\nM190 S[bed_temperature_initial_layer_single] ; 等待热床温度稳定\nM109 S[nozzle_temperature_initial_layer] ; 等待喷嘴温度稳定\nG1 Z0.28 F240 ; 喷嘴下降至打印高度\nG92 E0 ; 挤出机归零\nG1 Y140 E10 F1500 ; 画第一条 priming 线\nG1 X2.3 F5000 ; X轴微调\nG92 E0 ; 挤出机归零\nG1 Y10 E10 F1200 ; 画第二条 priming 线\nG92 E0 ; 最终挤出机归零",
|
||||||
|
|
||||||
|
"retract_before_wipe": [
|
||||||
|
"100%"
|
||||||
|
],
|
||||||
|
"retract_length_toolchange": [
|
||||||
|
"10"
|
||||||
|
],
|
||||||
|
"retraction_length": [
|
||||||
|
"0.5"
|
||||||
|
],
|
||||||
|
"retraction_minimum_travel": [
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"retraction_speed": [
|
||||||
|
"50"
|
||||||
|
],
|
||||||
|
"thumbnails": "300x300/PNG",
|
||||||
|
"z_hop": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_travel": [
|
||||||
|
"10000",
|
||||||
|
"10000"
|
||||||
|
],
|
||||||
|
"max_layer_height": [
|
||||||
|
"0.16"
|
||||||
|
],
|
||||||
|
"min_layer_height": [
|
||||||
|
"0.04"
|
||||||
|
],
|
||||||
|
"upward_compatible_machine": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
"type": "machine",
|
||||||
|
"name": "Goofoo Velo+ 0.4 nozzle",
|
||||||
|
"inherits": "fdm_klipper_common",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "XzHz1vNOYg3Ofe8X",
|
||||||
|
"instantiation": "true",
|
||||||
|
"nozzle_diameter": [
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"nozzle_type": "hardened_steel",
|
||||||
|
"printer_model": "Goofoo Velo+",
|
||||||
|
"printer_variant": "0.4",
|
||||||
|
"default_filament_profile": [
|
||||||
|
"Goofoo ABS @System"
|
||||||
|
],
|
||||||
|
"default_print_profile": "0.20mm Standard @Goofoo Velo+",
|
||||||
|
"printable_area": [
|
||||||
|
"0x0",
|
||||||
|
"600x0",
|
||||||
|
"600x500",
|
||||||
|
"0x500"
|
||||||
|
],
|
||||||
|
"machine_end_gcode": "{if max_layer_z < printable_height}G1 Z{z_offset+min(max_layer_z+2, printable_height)} F600 ; Move print head up{endif}\nG1 X5 Y{print_bed_max[1]*0.8} F{travel_speed*60} ; present print\n{if max_layer_z < printable_height-10}G1 Z{z_offset+min(max_layer_z+70, printable_height-10)} F600 ; Move print head further up{endif}\n{if max_layer_z < max_print_height*0.6}G1 Z{printable_height*0.6} F600 ; Move print head further up{endif}\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107 ; turn off fan\nM84 X Y E ; disable motors",
|
||||||
|
"printable_height": "600",
|
||||||
|
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0",
|
||||||
|
"change_filament_gcode": "PAUSE",
|
||||||
|
"deretraction_speed": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"extruder_clearance_height_to_lid": "350",
|
||||||
|
"extruder_clearance_height_to_rod": "60",
|
||||||
|
"extruder_clearance_radius": "50",
|
||||||
|
"machine_max_acceleration_e": [
|
||||||
|
"20000",
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_retracting": [
|
||||||
|
"15000",
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_z": [
|
||||||
|
"100",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_e": [
|
||||||
|
"1",
|
||||||
|
"2.5"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_x": [
|
||||||
|
"5",
|
||||||
|
"9"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_y": [
|
||||||
|
"5",
|
||||||
|
"9"
|
||||||
|
],
|
||||||
|
"machine_max_speed_e": [
|
||||||
|
"50",
|
||||||
|
"25"
|
||||||
|
],
|
||||||
|
"machine_max_speed_x": [
|
||||||
|
"730",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_speed_y": [
|
||||||
|
"730",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_speed_z": [
|
||||||
|
"15",
|
||||||
|
"12"
|
||||||
|
],
|
||||||
|
"machine_start_gcode": "G90 ; 绝对坐标模式\nM83 ; 挤出机相对模式\nM140 S[first_layer_bed_temperature] ; 设置热床温度\nM104 S140 ; 临时喷嘴温度=切片首层设定(防止归零渗料)\nG4 S10 ; 预热等待10秒\nG28 ; 归零所有轴\nG1 Z50 F240 ; Z轴抬升\nG1 X2 Y10 F3000 ; 移动至起始位置\nM104 S[nozzle_temperature_initial_layer] ; 正式设置喷嘴温度(与切片设置一致)\nM190 S[bed_temperature_initial_layer_single] ; 等待热床温度稳定\nM109 S[nozzle_temperature_initial_layer] ; 等待喷嘴温度稳定\nG1 Z0.28 F240 ; 喷嘴下降至打印高度\nG92 E0 ; 挤出机归零\nG1 Y140 E10 F1500 ; 画第一条 priming 线\nG1 X2.3 F5000 ; X轴微调\nG92 E0 ; 挤出机归零\nG1 Y10 E10 F1200 ; 画第二条 priming 线\nG92 E0 ; 最终挤出机归零",
|
||||||
|
|
||||||
|
"retract_before_wipe": [
|
||||||
|
"100%"
|
||||||
|
],
|
||||||
|
"retract_length_toolchange": [
|
||||||
|
"10"
|
||||||
|
],
|
||||||
|
"retraction_length": [
|
||||||
|
"0.5"
|
||||||
|
],
|
||||||
|
"retraction_minimum_travel": [
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"retraction_speed": [
|
||||||
|
"50"
|
||||||
|
],
|
||||||
|
"thumbnails": "300x300/PNG",
|
||||||
|
"z_hop": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_travel": [
|
||||||
|
"10000",
|
||||||
|
"10000"
|
||||||
|
],
|
||||||
|
"upward_compatible_machine": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
{
|
||||||
|
"type": "machine",
|
||||||
|
"name": "Goofoo Velo+ 0.6 nozzle",
|
||||||
|
"inherits": "fdm_klipper_common",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "Pauu7JRq7u2OD6kv",
|
||||||
|
"instantiation": "true",
|
||||||
|
"nozzle_diameter": [
|
||||||
|
"0.6"
|
||||||
|
],
|
||||||
|
"nozzle_type": "hardened_steel",
|
||||||
|
"printer_model": "Goofoo Velo+",
|
||||||
|
"printer_variant": "0.6",
|
||||||
|
"default_filament_profile": [
|
||||||
|
"Goofoo ABS @System"
|
||||||
|
],
|
||||||
|
"default_print_profile": "0.30mm Standard @Goofoo Velo+",
|
||||||
|
"printable_area": [
|
||||||
|
"0x0",
|
||||||
|
"600x0",
|
||||||
|
"600x500",
|
||||||
|
"0x500"
|
||||||
|
],
|
||||||
|
"machine_end_gcode": "{if max_layer_z < printable_height}G1 Z{z_offset+min(max_layer_z+2, printable_height)} F600 ; Move print head up{endif}\nG1 X5 Y{print_bed_max[1]*0.8} F{travel_speed*60} ; present print\n{if max_layer_z < printable_height-10}G1 Z{z_offset+min(max_layer_z+70, printable_height-10)} F600 ; Move print head further up{endif}\n{if max_layer_z < max_print_height*0.6}G1 Z{printable_height*0.6} F600 ; Move print head further up{endif}\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107 ; turn off fan\nM84 X Y E ; disable motors",
|
||||||
|
"printable_height": "600",
|
||||||
|
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0",
|
||||||
|
"change_filament_gcode": "PAUSE",
|
||||||
|
"deretraction_speed": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"extruder_clearance_height_to_lid": "350",
|
||||||
|
"extruder_clearance_height_to_rod": "60",
|
||||||
|
"extruder_clearance_radius": "50",
|
||||||
|
"machine_max_acceleration_e": [
|
||||||
|
"20000",
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_retracting": [
|
||||||
|
"15000",
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_z": [
|
||||||
|
"100",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_e": [
|
||||||
|
"1",
|
||||||
|
"2.5"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_x": [
|
||||||
|
"5",
|
||||||
|
"9"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_y": [
|
||||||
|
"5",
|
||||||
|
"9"
|
||||||
|
],
|
||||||
|
"machine_max_speed_e": [
|
||||||
|
"50",
|
||||||
|
"25"
|
||||||
|
],
|
||||||
|
"machine_max_speed_x": [
|
||||||
|
"730",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_speed_y": [
|
||||||
|
"730",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_speed_z": [
|
||||||
|
"15",
|
||||||
|
"12"
|
||||||
|
],
|
||||||
|
"machine_start_gcode": "G90 ; 绝对坐标模式\nM83 ; 挤出机相对模式\nM140 S[first_layer_bed_temperature] ; 设置热床温度\nM104 S140 ; 临时喷嘴温度=切片首层设定(防止归零渗料)\nG4 S10 ; 预热等待10秒\nG28 ; 归零所有轴\nG1 Z50 F240 ; Z轴抬升\nG1 X2 Y10 F3000 ; 移动至起始位置\nM104 S[nozzle_temperature_initial_layer] ; 正式设置喷嘴温度(与切片设置一致)\nM190 S[bed_temperature_initial_layer_single] ; 等待热床温度稳定\nM109 S[nozzle_temperature_initial_layer] ; 等待喷嘴温度稳定\nG1 Z0.28 F240 ; 喷嘴下降至打印高度\nG92 E0 ; 挤出机归零\nG1 Y140 E10 F1500 ; 画第一条 priming 线\nG1 X2.3 F5000 ; X轴微调\nG92 E0 ; 挤出机归零\nG1 Y10 E10 F1200 ; 画第二条 priming 线\nG92 E0 ; 最终挤出机归零",
|
||||||
|
|
||||||
|
"retract_before_wipe": [
|
||||||
|
"100%"
|
||||||
|
],
|
||||||
|
"retract_length_toolchange": [
|
||||||
|
"10"
|
||||||
|
],
|
||||||
|
"retraction_length": [
|
||||||
|
"0.5"
|
||||||
|
],
|
||||||
|
"retraction_minimum_travel": [
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"retraction_speed": [
|
||||||
|
"50"
|
||||||
|
],
|
||||||
|
"thumbnails": "300x300/PNG",
|
||||||
|
"z_hop": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_travel": [
|
||||||
|
"10000",
|
||||||
|
"10000"
|
||||||
|
],
|
||||||
|
"max_layer_height": [
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"min_layer_height": [
|
||||||
|
"0.12"
|
||||||
|
],
|
||||||
|
"upward_compatible_machine": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
{
|
||||||
|
"type": "machine",
|
||||||
|
"name": "Goofoo Velo+ Tool 0.4",
|
||||||
|
"inherits": "Goofoo Velo+ 0.4 nozzle",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "ww39WeaF5nYks1em",
|
||||||
|
"instantiation": "true",
|
||||||
|
"nozzle_diameter": [
|
||||||
|
"0.4",
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"printer_model": "Goofoo Velo+ Tool",
|
||||||
|
"change_filament_gcode": "T[next_extruder]\n;M104 S[nozzle_temperature_initial_layer] T[next_extruder]; 正式设置喷嘴温度(与切片设置一致)\n;M109 S[nozzle_temperature_initial_layer] T[next_extruder] ; 等待喷嘴温度稳定\n",
|
||||||
|
"default_nozzle_volume_type": [
|
||||||
|
"Standard",
|
||||||
|
"Standard"
|
||||||
|
],
|
||||||
|
"deretraction_speed": [
|
||||||
|
"0",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"extruder_colour": [
|
||||||
|
"#FCE94F",
|
||||||
|
"#FCE94F"
|
||||||
|
],
|
||||||
|
"extruder_offset": [
|
||||||
|
"0x0",
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"extruder_printable_height": [
|
||||||
|
"0",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"extruder_type": [
|
||||||
|
"Direct Drive",
|
||||||
|
"Direct Drive"
|
||||||
|
],
|
||||||
|
"extruder_variant_list": [
|
||||||
|
"Direct Drive Standard",
|
||||||
|
"Direct Drive Standard"
|
||||||
|
],
|
||||||
|
"long_retractions_when_cut": [
|
||||||
|
"0",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"machine_end_gcode": "{if max_layer_z < printable_height}G1 Z{z_offset+min(max_layer_z+2, printable_height)} F600 ; Move print head up{endif}\nT0\nG1 X5 Y{print_bed_max[1]*0.8} F{travel_speed*60} ; present print\n{if max_layer_z < printable_height-10}G1 Z{z_offset+min(max_layer_z+70, printable_height-10)} F600 ; Move print head further up{endif}\n{if max_layer_z < max_print_height*0.6}G1 Z{printable_height*0.6} F600 ; Move print head further up{endif}\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107 ; turn off fan\nM84 X Y E ; disable motors",
|
||||||
|
"machine_start_gcode": "G90 ; 绝对坐标模式\nM83 ; 挤出机相对模式\nM140 S[first_layer_bed_temperature] ; 设置热床温度\nM104 S140 T0 ; 临时喷嘴温度=切片首层设定(防止归零渗料)\nM104 S140 T1\nG4 S10 ; 预热等待10秒\nG28 ; 归零所有轴\nG1 Z50 F240 ; Z轴抬升\nG1 X2 Y10 F3000 ; 移动至起始位置\nM104 S[nozzle_temperature_initial_layer] T0 ; 正式设置喷嘴温度(与切片设置一致)\nM104 S[nozzle_temperature_initial_layer] T1 ; 正式设置喷嘴温度(与切片设置一致)\nM190 S[bed_temperature_initial_layer_single] T0; 等待热床温度稳定\nM109 S[nozzle_temperature_initial_layer] T0 ; 等待喷嘴温度稳定\nG1 Z0.28 F240 ; 喷嘴下降至打印高度\nG92 E0 ; 挤出机归零\nG1 Y140 E10 F1500 ; 画第一条 priming 线\nG1 X2.3 F5000 ; X轴微调\nG92 E0 ; 挤出机归零\nG1 Y10 E10 F1200 ; 画第二条 priming 线\nG92 E0 ; 最终挤出机归零\nG1 Z20 F240 ; Z轴抬升\nG1 X280 Y10 F3000 ; 移动至起始位置\nT1\nM104 S[nozzle_temperature_initial_layer] T1 ; 正式设置喷嘴温度(与切片设置一致)\nM190 S[bed_temperature_initial_layer_single] T1; 等待热床温度稳定\nM109 S[nozzle_temperature_initial_layer] T1; 等待喷嘴温度稳定\nG4 S10 ; 预热等待10秒\nG1 Z0.28 F240 ; 喷嘴下降至打印高度\nG92 E0 ; 挤出机归零\nG1 Y140 E10 F1500 ; 画第一条 priming 线\nG1 X282.3 F5000 ; X轴微调\nG92 E0 ; 挤出机归零\nG1 Y10 E10 F1200 ; 画第二条 priming 线\nG92 E0 ; 最终挤出机归零",
|
||||||
|
"max_layer_height": [
|
||||||
|
"0.32",
|
||||||
|
"0.32"
|
||||||
|
],
|
||||||
|
"min_layer_height": [
|
||||||
|
"0.08",
|
||||||
|
"0.08"
|
||||||
|
],
|
||||||
|
"nozzle_flush_dataset": [
|
||||||
|
"nil",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"nozzle_type": [
|
||||||
|
"nil",
|
||||||
|
"hardened_steel"
|
||||||
|
],
|
||||||
|
"nozzle_volume": [
|
||||||
|
"nil",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"printable_area": [
|
||||||
|
"0x0",
|
||||||
|
"300x0",
|
||||||
|
"300x300",
|
||||||
|
"0x300"
|
||||||
|
],
|
||||||
|
"printer_extruder_id": [
|
||||||
|
"1",
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"printer_extruder_variant": [
|
||||||
|
"Direct Drive Standard",
|
||||||
|
"Direct Drive Standard"
|
||||||
|
],
|
||||||
|
"purge_in_prime_tower": "0",
|
||||||
|
"retract_before_wipe": [
|
||||||
|
"100%",
|
||||||
|
"100%"
|
||||||
|
],
|
||||||
|
"retract_length_toolchange": [
|
||||||
|
"10",
|
||||||
|
"10"
|
||||||
|
],
|
||||||
|
"retract_lift_above": [
|
||||||
|
"0",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"retract_lift_below": [
|
||||||
|
"0",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"retract_lift_enforce": [
|
||||||
|
"All Surfaces",
|
||||||
|
"All Surfaces"
|
||||||
|
],
|
||||||
|
"retract_restart_extra": [
|
||||||
|
"0",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"retract_restart_extra_toolchange": [
|
||||||
|
"0",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"retract_when_changing_layer": [
|
||||||
|
"1",
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"retraction_distances_when_cut": [
|
||||||
|
"18",
|
||||||
|
"18"
|
||||||
|
],
|
||||||
|
"retraction_length": [
|
||||||
|
"0.5",
|
||||||
|
"0.5"
|
||||||
|
],
|
||||||
|
"retraction_minimum_travel": [
|
||||||
|
"2",
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"retraction_speed": [
|
||||||
|
"50",
|
||||||
|
"50"
|
||||||
|
],
|
||||||
|
"single_extruder_multi_material": "0",
|
||||||
|
"travel_slope": [
|
||||||
|
"3",
|
||||||
|
"3"
|
||||||
|
],
|
||||||
|
"wipe": [
|
||||||
|
"1",
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"wipe_distance": [
|
||||||
|
"1",
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"z_hop": [
|
||||||
|
"0",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"z_hop_types": [
|
||||||
|
"Slope Lift",
|
||||||
|
"Slope Lift"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"type": "machine_model",
|
||||||
|
"name": "Goofoo Velo+ Tool",
|
||||||
|
"nozzle_diameter": "0.4",
|
||||||
|
"bed_model": "Goofoo-Velo+.stl",
|
||||||
|
"bed_texture": "Goofoo-Velo+-texture.svg",
|
||||||
|
"family": "Goofoo",
|
||||||
|
"machine_tech": "FFF",
|
||||||
|
"model_id": "V006",
|
||||||
|
"default_materials": "Goofoo ABS @System"
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"type": "machine_model",
|
||||||
|
"name": "Goofoo Velo+",
|
||||||
|
"nozzle_diameter": "0.2;0.4;0.6",
|
||||||
|
"bed_model": "Goofoo-Velo+.stl",
|
||||||
|
"bed_texture": "Goofoo-Velo+-texture.svg",
|
||||||
|
"family": "Goofoo",
|
||||||
|
"machine_tech": "FFF",
|
||||||
|
"model_id": "V001",
|
||||||
|
"default_materials": "Goofoo ABS @System"
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"type": "machine",
|
||||||
|
"name": "fdm_klipper_common",
|
||||||
|
"inherits": "fdm_machine_common",
|
||||||
|
"from": "system",
|
||||||
|
"instantiation": "false",
|
||||||
|
"gcode_flavor": "klipper",
|
||||||
|
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\nG92 E0.0\n;[layer_z]\n\n",
|
||||||
|
"machine_start_gcode": "G90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nM140 S{first_layer_bed_temperature[0]} ; set bed temp\nM104 S140 ; set temporary nozzle temp to prevent oozing\nG4 S10 ; wait 10s for preheating\nG28 ; home all axes\nG1 Z50 F240 ; raise Z axis\nG1 X2 Y10 F3000 ; move to start position\nM104 S{first_layer_temperature[0]} ; set final nozzle temp\nM190 S{first_layer_bed_temperature[0]} ; wait for bed temp\nM109 S{first_layer_temperature[0]} ; wait for nozzle temp\nG1 Z0.28 F240 ; lower nozzle to print height\nG92 E0 ; reset extrusion distance\nG1 Y140 E10 F1500 ; prime the nozzle\nG1 X2.3 F5000 ; slight X adjustment\nG92 E0 ; reset extrusion distance\nG1 Y10 E10 F1200 ; prime again\nG92 E0 ; final extrusion reset",
|
||||||
|
"machine_end_gcode": "{if max_layer_z < printable_height}G1 Z{z_offset+min(max_layer_z+2, printable_height)} F600 ; Move print head up{endif}\nG1 X5 Y{print_bed_max[1]*0.8} F{travel_speed*60} ; present print\n{if max_layer_z < printable_height-10}G1 Z{z_offset+min(max_layer_z+70, printable_height-10)} F600 ; Move print head further up{endif}\n{if max_layer_z < max_print_height*0.6}G1 Z{printable_height*0.6} F600 ; Move print head further up{endif}\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107 ; turn off fan\nM84 X Y E ; disable motors",
|
||||||
|
"change_filament_gcode": "PAUSE",
|
||||||
|
"machine_pause_gcode": "PAUSE"
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
{
|
||||||
|
"type": "machine",
|
||||||
|
"name": "fdm_machine_common",
|
||||||
|
"from": "system",
|
||||||
|
"instantiation": "false",
|
||||||
|
"printer_technology": "FFF",
|
||||||
|
"deretraction_speed": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"extruder_colour": [
|
||||||
|
"#FCE94F"
|
||||||
|
],
|
||||||
|
"extruder_offset": [
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"gcode_flavor": "marlin",
|
||||||
|
"machine_max_acceleration_e": [
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_extruding": [
|
||||||
|
"10000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_retracting": [
|
||||||
|
"1000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_x": [
|
||||||
|
"10000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_y": [
|
||||||
|
"10000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_z": [
|
||||||
|
"500"
|
||||||
|
],
|
||||||
|
"machine_max_speed_e": [
|
||||||
|
"60"
|
||||||
|
],
|
||||||
|
"machine_max_speed_x": [
|
||||||
|
"500"
|
||||||
|
],
|
||||||
|
"machine_max_speed_y": [
|
||||||
|
"500"
|
||||||
|
],
|
||||||
|
"machine_max_speed_z": [
|
||||||
|
"10"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_e": [
|
||||||
|
"5"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_x": [
|
||||||
|
"8"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_y": [
|
||||||
|
"8"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_z": [
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"machine_min_extruding_rate": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"machine_min_travel_rate": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"max_layer_height": [
|
||||||
|
"0.32"
|
||||||
|
],
|
||||||
|
"min_layer_height": [
|
||||||
|
"0.08"
|
||||||
|
],
|
||||||
|
"printable_height": "250",
|
||||||
|
"extruder_clearance_radius": "65",
|
||||||
|
"extruder_clearance_height_to_rod": "36",
|
||||||
|
"extruder_clearance_height_to_lid": "140",
|
||||||
|
"nozzle_diameter": [
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"printer_settings_id": "",
|
||||||
|
"printer_variant": "0.4",
|
||||||
|
"retraction_minimum_travel": [
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"retract_before_wipe": [
|
||||||
|
"70%"
|
||||||
|
],
|
||||||
|
"retract_when_changing_layer": [
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"retraction_length": [
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"retract_length_toolchange": [
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"z_hop": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"retract_restart_extra": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"retract_restart_extra_toolchange": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"retraction_speed": [
|
||||||
|
"60"
|
||||||
|
],
|
||||||
|
"single_extruder_multi_material": "1",
|
||||||
|
"change_filament_gcode": "",
|
||||||
|
"wipe": [
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"default_print_profile": "",
|
||||||
|
"machine_start_gcode": "G0 Z20 F9000\nG92 E0; G1 E-10 F1200\nG28\nM970 Q1 A10 B10 C130 K0\nM970 Q1 A10 B131 C250 K1\nM974 Q1 S1 P0\nM970 Q0 A10 B10 C130 H20 K0\nM970 Q0 A10 B131 C250 K1\nM974 Q0 S1 P0\nM220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\nG29 ;Home\nG90;\nG92 E0 ;Reset Extruder \nG1 Z2.0 F3000 ;Move Z Axis up \nG1 X10.1 Y20 Z0.28 F5000.0 ;Move to start position\nM109 S205;\nG1 X10.1 Y200.0 Z0.28 F1500.0 E15 ;Draw the first line\nG1 X10.4 Y200.0 Z0.28 F5000.0 ;Move to side a little\nG1 X10.4 Y20 Z0.28 F1500.0 E30 ;Draw the second line\nG92 E0 ;Reset Extruder \nG1 X110 Y110 Z2.0 F3000 ;Move Z Axis up",
|
||||||
|
"machine_end_gcode": "M400 ; wait for buffer to clear\nG92 E0 ; zero the extruder\nG1 E-4.0 F3600; retract \nG91\nG1 Z3;\nM104 S0 ; turn off hotend\nM140 S0 ; turn off bed\nM106 S0 ; turn off fan\nG90 \nG0 X110 Y200 F3600 \nprint_end",
|
||||||
|
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||||
|
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n",
|
||||||
|
"machine_pause_gcode": "M601"
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"type": "process",
|
||||||
|
"name": "0.10mm Standard @Goofoo Velo+",
|
||||||
|
"inherits": "0.20mm Standard @Goofoo Velo+",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "dJV4bTrLbOPzW2YB",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": [
|
||||||
|
"Goofoo Velo+ 0.2 nozzle"
|
||||||
|
],
|
||||||
|
"layer_height": "0.1",
|
||||||
|
"initial_layer_print_height": "0.1",
|
||||||
|
"bottom_shell_layers": "6",
|
||||||
|
"top_shell_layers": "12",
|
||||||
|
"wall_loops": "4",
|
||||||
|
"line_width": "0.22",
|
||||||
|
"initial_layer_line_width": "0.25",
|
||||||
|
"inner_wall_line_width": "0.22",
|
||||||
|
"outer_wall_line_width": "0.22",
|
||||||
|
"sparse_infill_line_width": "0.22",
|
||||||
|
"internal_solid_infill_line_width": "0.22",
|
||||||
|
"top_surface_line_width": "0.22",
|
||||||
|
"support_line_width": "0.22",
|
||||||
|
"support_top_z_distance": "0.1",
|
||||||
|
"support_bottom_z_distance": "0.1"
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"type": "process",
|
||||||
|
"name": "0.20mm Standard @Goofoo GP1",
|
||||||
|
"inherits": "fdm_process_klipper_common",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "zrscQBUxp6L7BlQ9",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": [
|
||||||
|
"Goofoo GP1 0.4 nozzle"
|
||||||
|
],
|
||||||
|
"layer_height": "0.2",
|
||||||
|
"accel_to_decel_enable": "0",
|
||||||
|
"bridge_acceleration": "100%",
|
||||||
|
"bridge_speed": "100",
|
||||||
|
"brim_type": "no_brim",
|
||||||
|
"gap_infill_speed": "250",
|
||||||
|
"initial_layer_acceleration": "1000",
|
||||||
|
"initial_layer_infill_speed": "100",
|
||||||
|
"initial_layer_speed": "70",
|
||||||
|
"inner_wall_speed": "250",
|
||||||
|
"internal_bridge_speed": "200%",
|
||||||
|
"internal_solid_infill_speed": "250",
|
||||||
|
"outer_wall_acceleration": "5000",
|
||||||
|
"outer_wall_speed": "100",
|
||||||
|
"overhang_4_4_speed": "0",
|
||||||
|
"reduce_crossing_wall": "1",
|
||||||
|
"skirt_distance": "6",
|
||||||
|
"skirt_height": "1",
|
||||||
|
"skirt_loops": "1",
|
||||||
|
"sparse_infill_pattern": "grid",
|
||||||
|
"sparse_infill_speed": "250",
|
||||||
|
"top_surface_acceleration": "5000",
|
||||||
|
"top_surface_speed": "200",
|
||||||
|
"travel_acceleration": "5000"
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"type": "process",
|
||||||
|
"name": "0.20mm Standard @Goofoo TurboM",
|
||||||
|
"inherits": "fdm_process_klipper_common",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "LVXxUtAcYGCY6irA",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": [
|
||||||
|
"Goofoo TurboM 0.4 nozzle"
|
||||||
|
],
|
||||||
|
"layer_height": "0.2",
|
||||||
|
"bridge_acceleration": "2000",
|
||||||
|
"bridge_speed": "60",
|
||||||
|
"brim_type": "no_brim",
|
||||||
|
"default_acceleration": "10000",
|
||||||
|
"enable_support": "1",
|
||||||
|
"exclude_object": "0",
|
||||||
|
"gap_infill_speed": "200",
|
||||||
|
"infill_anchor": "600%",
|
||||||
|
"infill_combination": "1",
|
||||||
|
"infill_wall_overlap": "15%",
|
||||||
|
"initial_layer_acceleration": "1000",
|
||||||
|
"initial_layer_line_width": "0.45",
|
||||||
|
"initial_layer_speed": "60",
|
||||||
|
"inner_wall_acceleration": "3000",
|
||||||
|
"inner_wall_line_width": "0.42",
|
||||||
|
"inner_wall_speed": "150",
|
||||||
|
"internal_solid_infill_acceleration": "3000",
|
||||||
|
"internal_solid_infill_line_width": "0.42",
|
||||||
|
"internal_solid_infill_speed": "150",
|
||||||
|
"line_width": "0.42",
|
||||||
|
"min_skirt_length": "0",
|
||||||
|
"minimum_sparse_infill_area": "70",
|
||||||
|
"only_one_wall_top": "1",
|
||||||
|
"outer_wall_acceleration": "2500",
|
||||||
|
"outer_wall_line_width": "0.42",
|
||||||
|
"outer_wall_speed": "100",
|
||||||
|
"reduce_crossing_wall": "1",
|
||||||
|
"skirt_distance": "6",
|
||||||
|
"skirt_height": "1",
|
||||||
|
"skirt_loops": "4",
|
||||||
|
"small_perimeter_speed": "200",
|
||||||
|
"sparse_infill_acceleration": "3000",
|
||||||
|
"sparse_infill_line_width": "0.42",
|
||||||
|
"sparse_infill_pattern": "grid",
|
||||||
|
"sparse_infill_speed": "150",
|
||||||
|
"support_base_pattern": "rectilinear",
|
||||||
|
"support_interface_bottom_layers": "-1",
|
||||||
|
"support_interface_pattern": "rectilinear",
|
||||||
|
"support_interface_spacing": "0",
|
||||||
|
"support_interface_speed": "100",
|
||||||
|
"support_interface_top_layers": "3",
|
||||||
|
"support_line_width": "0.35",
|
||||||
|
"support_object_xy_distance": "0.21",
|
||||||
|
"support_speed": "100",
|
||||||
|
"thick_bridges": "1",
|
||||||
|
"top_shell_layers": "6",
|
||||||
|
"top_shell_thickness": "0",
|
||||||
|
"top_surface_acceleration": "2500",
|
||||||
|
"top_surface_line_width": "0.42",
|
||||||
|
"top_surface_speed": "150",
|
||||||
|
"travel_acceleration": "10000",
|
||||||
|
"travel_speed": "200",
|
||||||
|
"wall_generator": "classic",
|
||||||
|
"wall_loops": "2",
|
||||||
|
"wall_sequence": "outer wall/inner wall"
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"type": "process",
|
||||||
|
"name": "0.20mm Standard @Goofoo Velo+",
|
||||||
|
"inherits": "fdm_process_klipper_common",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "iWjynhCcvbF8MWev",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": [
|
||||||
|
"Goofoo Velo+ 0.4 nozzle",
|
||||||
|
"Goofoo Velo+ Tool 0.4"
|
||||||
|
],
|
||||||
|
"layer_height": "0.2",
|
||||||
|
"initial_layer_print_height": "0.2",
|
||||||
|
"bottom_shell_layers": "3",
|
||||||
|
"top_shell_layers": "6",
|
||||||
|
"bridge_acceleration": "2000",
|
||||||
|
"bridge_speed": "60",
|
||||||
|
"brim_type": "no_brim",
|
||||||
|
"default_acceleration": "10000",
|
||||||
|
"enable_support": "1",
|
||||||
|
"exclude_object": "0",
|
||||||
|
"gap_infill_speed": "200",
|
||||||
|
"infill_anchor": "600%",
|
||||||
|
"infill_combination": "1",
|
||||||
|
"infill_wall_overlap": "15%",
|
||||||
|
"initial_layer_acceleration": "1000",
|
||||||
|
"initial_layer_line_width": "0.45",
|
||||||
|
"initial_layer_speed": "60",
|
||||||
|
"inner_wall_acceleration": "3000",
|
||||||
|
"inner_wall_line_width": "0.42",
|
||||||
|
"inner_wall_speed": "150",
|
||||||
|
"internal_solid_infill_acceleration": "3000",
|
||||||
|
"internal_solid_infill_line_width": "0.42",
|
||||||
|
"internal_solid_infill_speed": "150",
|
||||||
|
"line_width": "0.42",
|
||||||
|
"min_skirt_length": "0",
|
||||||
|
"minimum_sparse_infill_area": "70",
|
||||||
|
"only_one_wall_top": "1",
|
||||||
|
"outer_wall_acceleration": "2500",
|
||||||
|
"outer_wall_line_width": "0.42",
|
||||||
|
"outer_wall_speed": "100",
|
||||||
|
"reduce_crossing_wall": "1",
|
||||||
|
"skirt_distance": "6",
|
||||||
|
"skirt_height": "1",
|
||||||
|
"skirt_loops": "4",
|
||||||
|
"small_perimeter_speed": "200",
|
||||||
|
"sparse_infill_acceleration": "3000",
|
||||||
|
"sparse_infill_line_width": "0.42",
|
||||||
|
"sparse_infill_pattern": "grid",
|
||||||
|
"sparse_infill_speed": "150",
|
||||||
|
"support_base_pattern": "rectilinear",
|
||||||
|
"support_interface_bottom_layers": "-1",
|
||||||
|
"support_interface_pattern": "rectilinear",
|
||||||
|
"support_interface_spacing": "0",
|
||||||
|
"support_interface_speed": "100",
|
||||||
|
"support_interface_top_layers": "3",
|
||||||
|
"support_line_width": "0.35",
|
||||||
|
"support_object_xy_distance": "0.21",
|
||||||
|
"support_speed": "100",
|
||||||
|
"thick_bridges": "1",
|
||||||
|
"top_shell_thickness": "0",
|
||||||
|
"top_surface_acceleration": "2500",
|
||||||
|
"top_surface_line_width": "0.42",
|
||||||
|
"top_surface_speed": "150",
|
||||||
|
"travel_acceleration": "10000",
|
||||||
|
"travel_speed": "300",
|
||||||
|
"wall_generator": "classic",
|
||||||
|
"wall_loops": "2",
|
||||||
|
"wall_sequence": "outer wall/inner wall"
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"type": "process",
|
||||||
|
"name": "0.30mm Standard @Goofoo Velo+",
|
||||||
|
"inherits": "fdm_process_klipper_common",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "mi8ogYOYpqb2x60z",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": [
|
||||||
|
"Goofoo Velo+ 0.6 nozzle"
|
||||||
|
],
|
||||||
|
"initial_layer_print_height": "0.3",
|
||||||
|
"layer_height": "0.3"
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
{
|
||||||
|
"type": "process",
|
||||||
|
"name": "fdm_process_common",
|
||||||
|
"from": "system",
|
||||||
|
"instantiation": "false",
|
||||||
|
"seam_position": "aligned",
|
||||||
|
"resolution": "0.012",
|
||||||
|
"elefant_foot_compensation": "0.2",
|
||||||
|
"precise_outer_wall": "1",
|
||||||
|
"ironing_type": "no ironing",
|
||||||
|
"ironing_speed": "30",
|
||||||
|
"ironing_flow": "10%",
|
||||||
|
"ironing_spacing": "0.15",
|
||||||
|
"reduce_crossing_wall": "1",
|
||||||
|
"max_travel_detour_distance": "0",
|
||||||
|
"detect_overhang_wall": "1",
|
||||||
|
"wall_loops": "3",
|
||||||
|
"top_surface_pattern": "monotonicline",
|
||||||
|
"top_shell_layers": "5",
|
||||||
|
"top_shell_thickness": "0.7",
|
||||||
|
"bottom_surface_pattern": "monotonic",
|
||||||
|
"bottom_shell_layers": "4",
|
||||||
|
"bottom_shell_thickness": "0.5",
|
||||||
|
"sparse_infill_density": "15%",
|
||||||
|
"sparse_infill_pattern": "crosshatch",
|
||||||
|
"infill_wall_overlap": "23%",
|
||||||
|
"infill_direction": "45",
|
||||||
|
"minimum_sparse_infill_area": "15",
|
||||||
|
"infill_combination": "0",
|
||||||
|
"ensure_vertical_shell_thickness": "1",
|
||||||
|
"initial_layer_speed": "50",
|
||||||
|
"initial_layer_infill_speed": "50",
|
||||||
|
"initial_layer_travel_speed": "100%",
|
||||||
|
"outer_wall_speed": "125",
|
||||||
|
"inner_wall_speed": "250",
|
||||||
|
"sparse_infill_speed": "250",
|
||||||
|
"internal_solid_infill_speed": "200",
|
||||||
|
"top_surface_speed": "150",
|
||||||
|
"gap_infill_speed": "150",
|
||||||
|
"support_speed": "125",
|
||||||
|
"support_interface_speed": "80",
|
||||||
|
"enable_overhang_speed": "1",
|
||||||
|
"overhang_1_4_speed": "0",
|
||||||
|
"overhang_2_4_speed": "50",
|
||||||
|
"overhang_3_4_speed": "25",
|
||||||
|
"overhang_4_4_speed": "15",
|
||||||
|
"bridge_speed": "50",
|
||||||
|
"travel_speed": "300",
|
||||||
|
"default_acceleration": "2500",
|
||||||
|
"outer_wall_acceleration": "500",
|
||||||
|
"inner_wall_acceleration": "500",
|
||||||
|
"bridge_acceleration": "500",
|
||||||
|
"travel_acceleration": "2500",
|
||||||
|
"default_jerk": "15",
|
||||||
|
"outer_wall_jerk": "10",
|
||||||
|
"inner_wall_jerk": "15",
|
||||||
|
"infill_jerk": "15",
|
||||||
|
"top_surface_jerk": "12",
|
||||||
|
"initial_layer_jerk": "12",
|
||||||
|
"travel_jerk": "20",
|
||||||
|
"enable_support": "0",
|
||||||
|
"support_type": "normal(auto)",
|
||||||
|
"support_threshold_angle": "30",
|
||||||
|
"support_on_build_plate_only": "0",
|
||||||
|
"raft_layers": "0",
|
||||||
|
"support_filament": "0",
|
||||||
|
"support_interface_filament": "0",
|
||||||
|
"support_top_z_distance": "0.2",
|
||||||
|
"support_base_pattern": "default",
|
||||||
|
"support_base_pattern_spacing": "2.5",
|
||||||
|
"support_interface_top_layers": "2",
|
||||||
|
"support_interface_bottom_layers": "2",
|
||||||
|
"support_interface_spacing": "0.5",
|
||||||
|
"support_object_xy_distance": "0.5",
|
||||||
|
"bridge_no_support": "0",
|
||||||
|
"independent_support_layer_height": "1",
|
||||||
|
"tree_support_branch_angle": "30",
|
||||||
|
"tree_support_wall_count": "0",
|
||||||
|
"skirt_loops": "2",
|
||||||
|
"skirt_distance": "3",
|
||||||
|
"skirt_height": "1",
|
||||||
|
"brim_width": "5",
|
||||||
|
"brim_object_gap": "0.1",
|
||||||
|
"enable_prime_tower": "0",
|
||||||
|
"prime_tower_width": "60",
|
||||||
|
"wipe_tower_no_sparse_layers": "0",
|
||||||
|
"print_sequence": "by layer",
|
||||||
|
"spiral_mode": "0",
|
||||||
|
"reduce_infill_retraction": "1",
|
||||||
|
"filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}_{printer_model}.gcode"
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"type": "process",
|
||||||
|
"name": "fdm_process_klipper_common",
|
||||||
|
"inherits": "fdm_process_common",
|
||||||
|
"from": "system",
|
||||||
|
"instantiation": "false",
|
||||||
|
"default_acceleration": "5000",
|
||||||
|
"top_surface_acceleration": "3000",
|
||||||
|
"travel_acceleration": "7000",
|
||||||
|
"inner_wall_acceleration": "5000",
|
||||||
|
"outer_wall_acceleration": "3000",
|
||||||
|
"initial_layer_acceleration": "500",
|
||||||
|
"initial_layer_speed": "50",
|
||||||
|
"initial_layer_infill_speed": "105",
|
||||||
|
"outer_wall_speed": "120",
|
||||||
|
"inner_wall_speed": "200",
|
||||||
|
"internal_solid_infill_speed": "200",
|
||||||
|
"top_surface_speed": "100",
|
||||||
|
"gap_infill_speed": "100",
|
||||||
|
"sparse_infill_speed": "200",
|
||||||
|
"travel_speed": "350",
|
||||||
|
"exclude_object": "1"
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "OrcaFilamentLibrary",
|
"name": "OrcaFilamentLibrary",
|
||||||
"version": "02.04.00.15",
|
"version": "02.04.00.16",
|
||||||
"force_update": "0",
|
"force_update": "0",
|
||||||
"description": "Orca Filament Library",
|
"description": "Orca Filament Library",
|
||||||
"filament_list": [
|
"filament_list": [
|
||||||
@@ -1396,6 +1396,10 @@
|
|||||||
"name": "addnorth ABS rABS",
|
"name": "addnorth ABS rABS",
|
||||||
"sub_path": "filament/addnorth/BBL/addnorth ABS rABS.json"
|
"sub_path": "filament/addnorth/BBL/addnorth ABS rABS.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo ABS @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo ABS @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "NIT ABS @System",
|
"name": "NIT ABS @System",
|
||||||
"sub_path": "filament/NIT/NIT ABS @System.json"
|
"sub_path": "filament/NIT/NIT ABS @System.json"
|
||||||
@@ -1480,6 +1484,10 @@
|
|||||||
"name": "Elegoo ASA-CF @base",
|
"name": "Elegoo ASA-CF @base",
|
||||||
"sub_path": "filament/Elegoo/Elegoo ASA-CF @base.json"
|
"sub_path": "filament/Elegoo/Elegoo ASA-CF @base.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo ASA @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo ASA @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Overture ASA @BBL X1",
|
"name": "Overture ASA @BBL X1",
|
||||||
"sub_path": "filament/Overture/BBL/Overture ASA @BBL X1.json"
|
"sub_path": "filament/Overture/BBL/Overture ASA @BBL X1.json"
|
||||||
@@ -1636,10 +1644,22 @@
|
|||||||
"name": "addnorth PVDF Adamant S1",
|
"name": "addnorth PVDF Adamant S1",
|
||||||
"sub_path": "filament/addnorth/BBL/addnorth PVDF Adamant S1.json"
|
"sub_path": "filament/addnorth/BBL/addnorth PVDF Adamant S1.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PA @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PA @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "addnorth PA-CF Adura X",
|
"name": "addnorth PA-CF Adura X",
|
||||||
"sub_path": "filament/addnorth/BBL/addnorth PA-CF Adura X.json"
|
"sub_path": "filament/addnorth/BBL/addnorth PA-CF Adura X.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PA-CF @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PA-CF @base.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PA-GF10 @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PA-GF10 @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "INSLOGIC PA6-66 @BBL P2S",
|
"name": "INSLOGIC PA6-66 @BBL P2S",
|
||||||
"sub_path": "filament/INSLOGIC/BBL/INSLOGIC PA6-66 @BBL P2S.json"
|
"sub_path": "filament/INSLOGIC/BBL/INSLOGIC PA6-66 @BBL P2S.json"
|
||||||
@@ -1712,6 +1732,10 @@
|
|||||||
"name": "COEX PCTG PRIME @System",
|
"name": "COEX PCTG PRIME @System",
|
||||||
"sub_path": "filament/COEX/COEX PCTG PRIME @System.json"
|
"sub_path": "filament/COEX/COEX PCTG PRIME @System.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PE @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PE @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Generic PE-CF @System",
|
"name": "Generic PE-CF @System",
|
||||||
"sub_path": "filament/Generic PE-CF @System.json"
|
"sub_path": "filament/Generic PE-CF @System.json"
|
||||||
@@ -2176,6 +2200,10 @@
|
|||||||
"name": "addnorth PETG rPETG Matte",
|
"name": "addnorth PETG rPETG Matte",
|
||||||
"sub_path": "filament/addnorth/BBL/addnorth PETG rPETG Matte.json"
|
"sub_path": "filament/addnorth/BBL/addnorth PETG rPETG Matte.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PETG @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PETG @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Generic PETG HF @System",
|
"name": "Generic PETG HF @System",
|
||||||
"sub_path": "filament/Generic PETG HF @System.json"
|
"sub_path": "filament/Generic PETG HF @System.json"
|
||||||
@@ -2236,6 +2264,10 @@
|
|||||||
"name": "eSUN PETG @System",
|
"name": "eSUN PETG @System",
|
||||||
"sub_path": "filament/eSUN/eSUN PETG @System.json"
|
"sub_path": "filament/eSUN/eSUN PETG @System.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PHA @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PHA @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "AliZ PLA @P1-X1",
|
"name": "AliZ PLA @P1-X1",
|
||||||
"sub_path": "filament/AliZ/BBL/AliZ PLA @P1-X1.json"
|
"sub_path": "filament/AliZ/BBL/AliZ PLA @P1-X1.json"
|
||||||
@@ -3120,6 +3152,10 @@
|
|||||||
"name": "addnorth PLA rPLA RE-ADD",
|
"name": "addnorth PLA rPLA RE-ADD",
|
||||||
"sub_path": "filament/addnorth/BBL/addnorth PLA rPLA RE-ADD.json"
|
"sub_path": "filament/addnorth/BBL/addnorth PLA rPLA RE-ADD.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PLA @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PLA @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "addnorth PLA X-PLA High Speed",
|
"name": "addnorth PLA X-PLA High Speed",
|
||||||
"sub_path": "filament/addnorth/BBL/addnorth PLA X-PLA High Speed.json"
|
"sub_path": "filament/addnorth/BBL/addnorth PLA X-PLA High Speed.json"
|
||||||
@@ -3136,6 +3172,10 @@
|
|||||||
"name": "addnorth PLA-CF Carbon Fiber",
|
"name": "addnorth PLA-CF Carbon Fiber",
|
||||||
"sub_path": "filament/addnorth/BBL/addnorth PLA-CF Carbon Fiber.json"
|
"sub_path": "filament/addnorth/BBL/addnorth PLA-CF Carbon Fiber.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PLA-CF @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PLA-CF @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "INSLOGIC PLA Pro @BBL P2S",
|
"name": "INSLOGIC PLA Pro @BBL P2S",
|
||||||
"sub_path": "filament/INSLOGIC/BBL/INSLOGIC PLA Pro @BBL P2S.json"
|
"sub_path": "filament/INSLOGIC/BBL/INSLOGIC PLA Pro @BBL P2S.json"
|
||||||
@@ -3820,6 +3860,10 @@
|
|||||||
"name": "FILL3D PPCF @System",
|
"name": "FILL3D PPCF @System",
|
||||||
"sub_path": "filament/FILL3D/FILL3D PPCF @System.json"
|
"sub_path": "filament/FILL3D/FILL3D PPCF @System.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PP @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PP @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Generic PP-CF @System",
|
"name": "Generic PP-CF @System",
|
||||||
"sub_path": "filament/Generic PP-CF @System.json"
|
"sub_path": "filament/Generic PP-CF @System.json"
|
||||||
@@ -3840,6 +3884,10 @@
|
|||||||
"name": "Bambu PVA @System",
|
"name": "Bambu PVA @System",
|
||||||
"sub_path": "filament/Bambu/Bambu PVA @System.json"
|
"sub_path": "filament/Bambu/Bambu PVA @System.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PVA @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PVA @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "FDplast SBS @System",
|
"name": "FDplast SBS @System",
|
||||||
"sub_path": "filament/FDplast/FDplast SBS @System.json"
|
"sub_path": "filament/FDplast/FDplast SBS @System.json"
|
||||||
@@ -4112,6 +4160,14 @@
|
|||||||
"name": "addnorth TPU Pro Matte 95A",
|
"name": "addnorth TPU Pro Matte 95A",
|
||||||
"sub_path": "filament/addnorth/BBL/addnorth TPU Pro Matte 95A.json"
|
"sub_path": "filament/addnorth/BBL/addnorth TPU Pro Matte 95A.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo 90A TPU @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo 90A TPU @base.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo 95A TPU @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo 95A TPU @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Overture TPU @BBL A1",
|
"name": "Overture TPU @BBL A1",
|
||||||
"sub_path": "filament/Overture/BBL/Overture TPU @BBL A1.json"
|
"sub_path": "filament/Overture/BBL/Overture TPU @BBL A1.json"
|
||||||
@@ -4156,10 +4212,30 @@
|
|||||||
"name": "Overture TPU @System",
|
"name": "Overture TPU @System",
|
||||||
"sub_path": "filament/Overture/Overture TPU @System.json"
|
"sub_path": "filament/Overture/Overture TPU @System.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo ABS @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo ABS @System.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Elegoo ASA-CF @System",
|
"name": "Elegoo ASA-CF @System",
|
||||||
"sub_path": "filament/Elegoo/Elegoo ASA-CF @System.json"
|
"sub_path": "filament/Elegoo/Elegoo ASA-CF @System.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo ASA @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo ASA @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PA @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PA @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PA-CF @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PA-CF @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PA-GF10 @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PA-GF10 @System.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Elegoo PC-FR @System",
|
"name": "Elegoo PC-FR @System",
|
||||||
"sub_path": "filament/Elegoo/Elegoo PC-FR @System.json"
|
"sub_path": "filament/Elegoo/Elegoo PC-FR @System.json"
|
||||||
@@ -4176,6 +4252,14 @@
|
|||||||
"name": "COEX PCTG PRIME @BBL A1M 0.8 nozzle",
|
"name": "COEX PCTG PRIME @BBL A1M 0.8 nozzle",
|
||||||
"sub_path": "filament/COEX/BBL/COEX PCTG PRIME @BBL A1M 0.8 nozzle.json"
|
"sub_path": "filament/COEX/BBL/COEX PCTG PRIME @BBL A1M 0.8 nozzle.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PE @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PE @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PE-CF @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PE-CF @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "AliZ PETG-CF @P1-X1",
|
"name": "AliZ PETG-CF @P1-X1",
|
||||||
"sub_path": "filament/AliZ/BBL/AliZ PETG-CF @P1-X1.json"
|
"sub_path": "filament/AliZ/BBL/AliZ PETG-CF @P1-X1.json"
|
||||||
@@ -4236,10 +4320,26 @@
|
|||||||
"name": "Fiberon PETG-ESD @BBL X1",
|
"name": "Fiberon PETG-ESD @BBL X1",
|
||||||
"sub_path": "filament/Polymaker/BBL/Fiberon PETG-ESD @BBL X1.json"
|
"sub_path": "filament/Polymaker/BBL/Fiberon PETG-ESD @BBL X1.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PETG @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PETG @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PETG HF @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PETG HF @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "addnorth PETG-CF Rigid X",
|
"name": "addnorth PETG-CF Rigid X",
|
||||||
"sub_path": "filament/addnorth/BBL/addnorth PETG-CF Rigid X.json"
|
"sub_path": "filament/addnorth/BBL/addnorth PETG-CF Rigid X.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PETG-CF @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PETG-CF @base.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PHA @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PHA @System.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "COEX PLA @BBL A1M 0.2 nozzle",
|
"name": "COEX PLA @BBL A1M 0.2 nozzle",
|
||||||
"sub_path": "filament/COEX/BBL/COEX PLA @BBL A1M 0.2 nozzle.json"
|
"sub_path": "filament/COEX/BBL/COEX PLA @BBL A1M 0.2 nozzle.json"
|
||||||
@@ -4304,6 +4404,14 @@
|
|||||||
"name": "Elegoo Rapid PLA+ @System",
|
"name": "Elegoo Rapid PLA+ @System",
|
||||||
"sub_path": "filament/Elegoo/Elegoo Rapid PLA+ @System.json"
|
"sub_path": "filament/Elegoo/Elegoo Rapid PLA+ @System.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PLA @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PLA @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PLA-CF @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PLA-CF @System.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Numakers PLA+ @BBL A1",
|
"name": "Numakers PLA+ @BBL A1",
|
||||||
"sub_path": "filament/Numakers/BBL/Numakers PLA+ @BBL A1.json"
|
"sub_path": "filament/Numakers/BBL/Numakers PLA+ @BBL A1.json"
|
||||||
@@ -5188,9 +5296,61 @@
|
|||||||
"name": "COEX PLA+Silk @System",
|
"name": "COEX PLA+Silk @System",
|
||||||
"sub_path": "filament/COEX/COEX PLA+Silk @System.json"
|
"sub_path": "filament/COEX/COEX PLA+Silk @System.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PLA Silk @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PLA Silk @base.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PP @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PP @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PP-CF @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PP-CF @base.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PP-GF @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PP-GF @base.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PVA @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PVA @System.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Elegoo Rapid TPU 95A @System",
|
"name": "Elegoo Rapid TPU 95A @System",
|
||||||
"sub_path": "filament/Elegoo/Elegoo Rapid TPU 95A @System.json"
|
"sub_path": "filament/Elegoo/Elegoo Rapid TPU 95A @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo 90A TPU @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo 90A TPU @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo 95A TPU @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo 95A TPU @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PE-CF @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PE-CF @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PETG HF @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PETG HF @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PETG-CF @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PETG-CF @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PLA Silk @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PLA Silk @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PP-CF @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PP-CF @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PP-GF @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PP-GF @System.json"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"process_list": [],
|
"process_list": [],
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo 90A TPU @System",
|
||||||
|
"inherits": "Goofoo 90A TPU @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "tjq76AQe9SCzZVo6",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo 90A TPU @base",
|
||||||
|
"inherits": "Generic TPU @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFlVOXX3",
|
||||||
|
"instantiation": "false",
|
||||||
|
"fan_cooling_layer_time": [
|
||||||
|
"60"
|
||||||
|
],
|
||||||
|
"fan_min_speed": [
|
||||||
|
"35"
|
||||||
|
],
|
||||||
|
"filament_max_volumetric_speed": [
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"hot_plate_temp": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"hot_plate_temp_initial_layer": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"nozzle_temperature": [
|
||||||
|
"220"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_initial_layer": [
|
||||||
|
"220"
|
||||||
|
],
|
||||||
|
"reduce_fan_stop_start_freq": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"slow_down_layer_time": [
|
||||||
|
"5"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"TPU"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo 95A TPU @System",
|
||||||
|
"inherits": "Goofoo 95A TPU @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "2YTvrE1gu1yHHTNM",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo 95A TPU @base",
|
||||||
|
"inherits": "Generic TPU @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFk50t30",
|
||||||
|
"instantiation": "false",
|
||||||
|
"fan_cooling_layer_time": [
|
||||||
|
"60"
|
||||||
|
],
|
||||||
|
"fan_min_speed": [
|
||||||
|
"35"
|
||||||
|
],
|
||||||
|
"hot_plate_temp": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"hot_plate_temp_initial_layer": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"nozzle_temperature": [
|
||||||
|
"220"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_initial_layer": [
|
||||||
|
"220"
|
||||||
|
],
|
||||||
|
"reduce_fan_stop_start_freq": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"slow_down_layer_time": [
|
||||||
|
"5"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"TPU"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo ABS @System",
|
||||||
|
"inherits": "Goofoo ABS @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "UeINgPGH4eaSJinC",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo ABS @base",
|
||||||
|
"inherits": "Generic ABS @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFd3PmW4",
|
||||||
|
"instantiation": "false",
|
||||||
|
"close_fan_the_first_x_layers": [
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"hot_plate_temp": [
|
||||||
|
"110"
|
||||||
|
],
|
||||||
|
"hot_plate_temp_initial_layer": [
|
||||||
|
"110"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"ABS"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo ASA @System",
|
||||||
|
"inherits": "Goofoo ASA @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "9ohqNl44uygTPfS9",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo ASA @base",
|
||||||
|
"inherits": "Generic ASA @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFyncHIG",
|
||||||
|
"instantiation": "false",
|
||||||
|
"filament_flow_ratio": [
|
||||||
|
"0.93"
|
||||||
|
],
|
||||||
|
"hot_plate_temp_initial_layer": [
|
||||||
|
"100"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"ASA"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PA @System",
|
||||||
|
"inherits": "Goofoo PA @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "1ximJVgZZfGmGyzD",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PA @base",
|
||||||
|
"inherits": "Generic PA @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFc2x7Ly",
|
||||||
|
"instantiation": "false",
|
||||||
|
"filament_max_volumetric_speed": [
|
||||||
|
"12"
|
||||||
|
],
|
||||||
|
"reduce_fan_stop_start_freq": [
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PA"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PA-CF @System",
|
||||||
|
"inherits": "Goofoo PA-CF @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "US4zznMNti8tLUHG",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PA-CF @base",
|
||||||
|
"inherits": "Generic PA-CF @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OF7skcRj",
|
||||||
|
"instantiation": "false",
|
||||||
|
"filament_max_volumetric_speed": [
|
||||||
|
"12"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PA-CF"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PA-GF10 @System",
|
||||||
|
"inherits": "Goofoo PA-GF10 @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "Lea3hf2lQG0M9eTC",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PA-GF10 @base",
|
||||||
|
"inherits": "Generic PA-CF @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFtjqw2h",
|
||||||
|
"instantiation": "false",
|
||||||
|
"nozzle_temperature": [
|
||||||
|
"230"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_initial_layer": [
|
||||||
|
"230"
|
||||||
|
],
|
||||||
|
"reduce_fan_stop_start_freq": [
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PA-GF"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PE @System",
|
||||||
|
"inherits": "Goofoo PE @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "muNxvgr0vdavMaOJ",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PE @base",
|
||||||
|
"inherits": "Generic PE @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFAln723",
|
||||||
|
"instantiation": "false",
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PE"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PE-CF @System",
|
||||||
|
"inherits": "Goofoo PE-CF @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "58fMbg6AClvalw5o",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PE-CF @base",
|
||||||
|
"inherits": "Generic PE-CF @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OF4Ll1Sk",
|
||||||
|
"instantiation": "false",
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PE-CF"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PETG @System",
|
||||||
|
"inherits": "Goofoo PETG @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "a9OKgJeoJgzfZiAB",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PETG @base",
|
||||||
|
"inherits": "Generic PETG @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFHdIbiu",
|
||||||
|
"instantiation": "false",
|
||||||
|
"hot_plate_temp": [
|
||||||
|
"80"
|
||||||
|
],
|
||||||
|
"hot_plate_temp_initial_layer": [
|
||||||
|
"80"
|
||||||
|
],
|
||||||
|
"nozzle_temperature": [
|
||||||
|
"240"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_initial_layer": [
|
||||||
|
"240"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PETG"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PETG HF @System",
|
||||||
|
"inherits": "Goofoo PETG HF @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "eMjhTRf5qPeeqSgk",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PETG HF @base",
|
||||||
|
"inherits": "Generic PETG HF @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OF5bjxyx",
|
||||||
|
"instantiation": "false",
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PETG HF"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PETG-CF @System",
|
||||||
|
"inherits": "Goofoo PETG-CF @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "ULNFo6zjgjc5MEIa",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PETG-CF @base",
|
||||||
|
"inherits": "Generic PETG-CF @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFo6Ff38",
|
||||||
|
"instantiation": "false",
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PETG-CF"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PHA @System",
|
||||||
|
"inherits": "Goofoo PHA @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "cfdeMyqld72Lqh38",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PHA @base",
|
||||||
|
"inherits": "Generic PHA @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFicmNhN",
|
||||||
|
"instantiation": "false",
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PHA"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PLA @System",
|
||||||
|
"inherits": "Goofoo PLA @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "UFWRE1b3706Cu1Iq",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PLA @base",
|
||||||
|
"inherits": "Generic PLA @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFoq0Cmt",
|
||||||
|
"instantiation": "false",
|
||||||
|
"hot_plate_temp": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"hot_plate_temp_initial_layer": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"nozzle_temperature": [
|
||||||
|
"230"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_initial_layer": [
|
||||||
|
"230"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PLA"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PLA Silk @System",
|
||||||
|
"inherits": "Goofoo PLA Silk @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "2pW2NEoY4hFCkNun",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PLA Silk @base",
|
||||||
|
"inherits": "Generic PLA Silk @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFpta2gc",
|
||||||
|
"instantiation": "false",
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PLA Silk"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PLA-CF @System",
|
||||||
|
"inherits": "Goofoo PLA-CF @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "R0UMDUPY9ryat8gQ",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PLA-CF @base",
|
||||||
|
"inherits": "Generic PLA-CF @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFMNABGJ",
|
||||||
|
"instantiation": "false",
|
||||||
|
"filament_flow_ratio": [
|
||||||
|
"0.98"
|
||||||
|
],
|
||||||
|
"hot_plate_temp": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"hot_plate_temp_initial_layer": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"nozzle_temperature": [
|
||||||
|
"240"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_initial_layer": [
|
||||||
|
"240"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PLA-CF"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PP @System",
|
||||||
|
"inherits": "Goofoo PP @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "TaB8JUtjJWgRcNrv",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PP @base",
|
||||||
|
"inherits": "Generic PP @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFlqlUSx",
|
||||||
|
"instantiation": "false",
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PP"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PP-CF @System",
|
||||||
|
"inherits": "Goofoo PP-CF @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "uHocbocT6flJhlBz",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PP-CF @base",
|
||||||
|
"inherits": "Generic PP-CF @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFUUiuIV",
|
||||||
|
"instantiation": "false",
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PP-CF"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PP-GF @System",
|
||||||
|
"inherits": "Goofoo PP-GF @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "mns8gICV0aNwJ02L",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PP-GF @base",
|
||||||
|
"inherits": "Generic PP-GF @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFaOhzl8",
|
||||||
|
"instantiation": "false",
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PP-GF"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PVA @System",
|
||||||
|
"inherits": "Goofoo PVA @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "tUXbv31nYkXJ87OT",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PVA @base",
|
||||||
|
"inherits": "Generic PVA @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OF86rkt6",
|
||||||
|
"instantiation": "false",
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PVA"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,410 @@
|
|||||||
|
# /// script
|
||||||
|
# requires-python = ">=3.12"
|
||||||
|
#
|
||||||
|
# [tool.orcaslicer.plugin]
|
||||||
|
# id = "reload-local-bundle-demo"
|
||||||
|
# name = "Reload Local Bundle Demo"
|
||||||
|
# description = "Manages local filament, process, and printer profiles with live bundle reload."
|
||||||
|
# author = "OrcaSlicer contributors"
|
||||||
|
# version = "0.1.0"
|
||||||
|
# ///
|
||||||
|
"""A small interactive consumer of ``orca.host.preset.bundles.reload_local``."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import orca
|
||||||
|
|
||||||
|
|
||||||
|
BUNDLE_ID = "reload-local-bundle-demo"
|
||||||
|
STATE_FILE = ".reload-local-bundle-demo-state.json"
|
||||||
|
DEMO_PRESETS = (
|
||||||
|
{
|
||||||
|
"id": "filament",
|
||||||
|
"name": "Local Bundle Demo Filament",
|
||||||
|
"kind": "Filament",
|
||||||
|
"type": "filament",
|
||||||
|
"directory": "filament",
|
||||||
|
"collection": "filaments",
|
||||||
|
"setting_key": "filament_settings_id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "process",
|
||||||
|
"name": "Local Bundle Demo Process",
|
||||||
|
"kind": "Process",
|
||||||
|
"type": "process",
|
||||||
|
"directory": "process",
|
||||||
|
"collection": "prints",
|
||||||
|
"setting_key": "print_settings_id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "printer",
|
||||||
|
"name": "Local Bundle Demo Printer",
|
||||||
|
"kind": "Printer",
|
||||||
|
"type": "machine",
|
||||||
|
"directory": "machine",
|
||||||
|
"collection": "printers",
|
||||||
|
"setting_key": "printer_settings_id",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _data_dir():
|
||||||
|
parts = os.path.abspath(__file__).replace("\\", "/").split("/")
|
||||||
|
if "orca_plugins" not in parts:
|
||||||
|
raise RuntimeError("Install this file as a local OrcaSlicer plugin")
|
||||||
|
return "/".join(parts[: parts.index("orca_plugins")])
|
||||||
|
|
||||||
|
|
||||||
|
def _folder_from_preset_file(path):
|
||||||
|
parts = os.path.normpath(path or "").replace("\\", "/").split("/")
|
||||||
|
try:
|
||||||
|
return parts[parts.index("user") + 1]
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _bundle_dir(bundle):
|
||||||
|
for collection in (bundle.filaments, bundle.prints, bundle.printers):
|
||||||
|
for index in range(collection.size()):
|
||||||
|
folder = _folder_from_preset_file(collection.preset(index).file)
|
||||||
|
if folder:
|
||||||
|
return os.path.join(_data_dir(), "user", folder, "_local", BUNDLE_ID)
|
||||||
|
return os.path.join(_data_dir(), "user", "default", "_local", BUNDLE_ID)
|
||||||
|
|
||||||
|
|
||||||
|
def _source_path(bundle, item):
|
||||||
|
return os.path.join(_bundle_dir(bundle), item["directory"], item["name"] + ".json")
|
||||||
|
|
||||||
|
|
||||||
|
def _state_path(bundle):
|
||||||
|
return os.path.join(_bundle_dir(bundle), STATE_FILE)
|
||||||
|
|
||||||
|
|
||||||
|
def _write_json_atomic(path, value):
|
||||||
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||||
|
descriptor, temporary = tempfile.mkstemp(
|
||||||
|
prefix="." + os.path.basename(path) + ".", suffix=".tmp", dir=os.path.dirname(path)
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
with os.fdopen(descriptor, "w", encoding="utf-8") as stream:
|
||||||
|
json.dump(value, stream, ensure_ascii=False, indent=2)
|
||||||
|
stream.write("\n")
|
||||||
|
os.replace(temporary, path)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
os.unlink(temporary)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_file(path):
|
||||||
|
try:
|
||||||
|
os.unlink(path)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _read_state(bundle):
|
||||||
|
try:
|
||||||
|
with open(_state_path(bundle), "r", encoding="utf-8") as stream:
|
||||||
|
state = json.load(stream)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return {"profiles": {}}
|
||||||
|
except (OSError, ValueError) as error:
|
||||||
|
raise RuntimeError("The demo state file cannot be read: %s" % error)
|
||||||
|
if not isinstance(state, dict) or not isinstance(state.get("profiles"), dict):
|
||||||
|
raise RuntimeError("The demo state file has an invalid shape")
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def _profile_state(state, item):
|
||||||
|
profiles = state["profiles"]
|
||||||
|
profile = profiles.get(item["id"])
|
||||||
|
if profile is None:
|
||||||
|
profile = {}
|
||||||
|
profiles[item["id"]] = profile
|
||||||
|
if not isinstance(profile, dict):
|
||||||
|
raise RuntimeError("The demo state for %s has an invalid shape" % item["name"])
|
||||||
|
profile["enabled"] = profile.get("enabled") is True
|
||||||
|
profile["deleted"] = profile.get("deleted") is True
|
||||||
|
if profile["deleted"]:
|
||||||
|
profile["enabled"] = False
|
||||||
|
profile["parent_name"] = str(profile.get("parent_name") or "")
|
||||||
|
return profile
|
||||||
|
|
||||||
|
|
||||||
|
def _collection(bundle, item):
|
||||||
|
return getattr(bundle, item["collection"])
|
||||||
|
|
||||||
|
|
||||||
|
def _target_name(item):
|
||||||
|
return "_local/%s/%s" % (BUNDLE_ID, item["name"])
|
||||||
|
|
||||||
|
|
||||||
|
def _parent_name(bundle, item):
|
||||||
|
collection = _collection(bundle, item)
|
||||||
|
own_names = {
|
||||||
|
_target_name(candidate)
|
||||||
|
for candidate in DEMO_PRESETS
|
||||||
|
if candidate["collection"] == item["collection"]
|
||||||
|
}
|
||||||
|
candidates = [collection.selected_preset()]
|
||||||
|
candidates.extend(collection.preset(index) for index in range(collection.size()))
|
||||||
|
for preset in candidates:
|
||||||
|
if preset.name not in own_names:
|
||||||
|
return preset.name
|
||||||
|
raise RuntimeError("A parent %s preset is required for this demo" % item["kind"].lower())
|
||||||
|
|
||||||
|
|
||||||
|
def _parent_is_live(bundle, item, name):
|
||||||
|
return bool(name) and name not in {_target_name(item)} and _collection(bundle, item).find_preset(name) is not None
|
||||||
|
|
||||||
|
|
||||||
|
def _profile(item, parent_name):
|
||||||
|
return {
|
||||||
|
"type": item["type"],
|
||||||
|
"name": item["name"],
|
||||||
|
"from": "User",
|
||||||
|
"instantiation": "true",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"inherits": parent_name,
|
||||||
|
item["setting_key"]: item["name"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _reconcile(bundle, state):
|
||||||
|
for item in DEMO_PRESETS:
|
||||||
|
profile = _profile_state(state, item)
|
||||||
|
if profile["enabled"] and not profile["deleted"] and not _parent_is_live(bundle, item, profile["parent_name"]):
|
||||||
|
profile["parent_name"] = _parent_name(bundle, item)
|
||||||
|
|
||||||
|
for item in DEMO_PRESETS:
|
||||||
|
profile = _profile_state(state, item)
|
||||||
|
path = _source_path(bundle, item)
|
||||||
|
if profile["enabled"] and not profile["deleted"]:
|
||||||
|
_write_json_atomic(path, _profile(item, profile["parent_name"]))
|
||||||
|
else:
|
||||||
|
_remove_file(path)
|
||||||
|
_write_json_atomic(
|
||||||
|
os.path.join(_bundle_dir(bundle), "bundle_metadata.json"),
|
||||||
|
{"id": BUNDLE_ID, "name": "Reload Local Bundle Demo", "version": "0.1.0"},
|
||||||
|
)
|
||||||
|
_write_json_atomic(_state_path(bundle), state)
|
||||||
|
orca.host.preset.bundles.reload_local(BUNDLE_ID)
|
||||||
|
|
||||||
|
|
||||||
|
def _state_payload(bundle, state, message="", error=""):
|
||||||
|
items = []
|
||||||
|
for item in DEMO_PRESETS:
|
||||||
|
profile = _profile_state(state, item)
|
||||||
|
items.append(
|
||||||
|
{
|
||||||
|
"id": item["id"],
|
||||||
|
"name": item["name"],
|
||||||
|
"kind": item["kind"],
|
||||||
|
"enabled": profile["enabled"],
|
||||||
|
"deleted": profile["deleted"],
|
||||||
|
"loaded": _collection(bundle, item).find_preset(_target_name(item)) is not None,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"command": "state",
|
||||||
|
"items": items,
|
||||||
|
"bundles": orca.host.preset.bundles.list_ids(),
|
||||||
|
"message": message,
|
||||||
|
"error": error,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
PAGE = r"""<!doctype html>
|
||||||
|
<html><head><meta charset="utf-8"><style>
|
||||||
|
:root { color-scheme: light dark; font: 14px system-ui, sans-serif; }
|
||||||
|
body { margin: 0; padding: 16px; background: Canvas; color: CanvasText; }
|
||||||
|
.profiles { display: grid; gap: 9px; }
|
||||||
|
.profile { border: 1px solid rgba(127, 127, 127, .4); border-radius: 8px; padding: 11px; }
|
||||||
|
.top, .bottom { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
||||||
|
.top { margin-bottom: 10px; }
|
||||||
|
.name { font-weight: 650; }
|
||||||
|
.kind, .message { color: GrayText; font-size: 12px; }
|
||||||
|
.status { border-radius: 999px; padding: 3px 8px; font-size: 12px; white-space: nowrap; }
|
||||||
|
.loaded { background: #0b8f5a; color: white; }
|
||||||
|
.disabled, .deleted { background: rgba(127, 127, 127, .25); }
|
||||||
|
.missing { background: #be4b0a; color: white; }
|
||||||
|
.toggle { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; }
|
||||||
|
.toggle input { position: absolute; opacity: 0; pointer-events: none; }
|
||||||
|
.switch { width: 34px; height: 20px; border-radius: 999px; background: rgba(127, 127, 127, .45); position: relative; }
|
||||||
|
.switch::after { content: ""; position: absolute; top: 3px; left: 3px; width: 14px; height: 14px; border-radius: 50%; background: white; transition: transform .15s ease; }
|
||||||
|
.toggle input:checked + .switch { background: #008f7a; }
|
||||||
|
.toggle input:checked + .switch::after { transform: translateX(14px); }
|
||||||
|
.toggle input:disabled + .switch, button:disabled { opacity: .5; cursor: wait; }
|
||||||
|
button { border: 0; border-radius: 6px; padding: 6px 10px; font: inherit; cursor: pointer; background: #b64036; color: white; }
|
||||||
|
button.secondary { background: rgba(127, 127, 127, .25); color: CanvasText; }
|
||||||
|
.actions { display: flex; justify-content: flex-end; margin-top: 14px; }
|
||||||
|
.message { min-height: 18px; margin-top: 10px; }
|
||||||
|
.error { color: #d13c32; }
|
||||||
|
.help { margin: 0 0 12px; color: GrayText; font-size: 12px; line-height: 1.4; }
|
||||||
|
.help summary { color: CanvasText; cursor: pointer; font-weight: 650; }
|
||||||
|
.help p { margin: 7px 0 0; }
|
||||||
|
h2 { margin: 16px 0 8px; font-size: 13px; }
|
||||||
|
.bundles { display: grid; gap: 6px; }
|
||||||
|
.bundle { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
||||||
|
.bundle code { overflow-wrap: anywhere; }
|
||||||
|
</style></head><body>
|
||||||
|
<details class="help"><summary>How it works</summary><p>Each row controls one profile in this local bundle. A switch adds or removes its source file; Delete removes the demo record and Restore returns it disabled. Every action reloads this bundle across filament, process, and machine. Local bundles lists every bundle id that orca.host.preset.bundles.list_ids() reports; Reload calls orca.host.preset.bundles.reload_local() for that id.</p></details>
|
||||||
|
<div class="profiles" id="profiles"></div>
|
||||||
|
<h2>Local bundles</h2>
|
||||||
|
<div class="bundles" id="bundles"></div>
|
||||||
|
<div class="actions"><button class="secondary" id="close">Close</button></div>
|
||||||
|
<div class="message" id="message"></div>
|
||||||
|
<script>
|
||||||
|
'use strict';
|
||||||
|
const $ = id => document.getElementById(id);
|
||||||
|
let items = [];
|
||||||
|
let bundles = [];
|
||||||
|
let busy = false;
|
||||||
|
function esc(value) {
|
||||||
|
return String(value == null ? '' : value).replace(/[&<>"']/g, c =>
|
||||||
|
({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||||
|
}
|
||||||
|
function status(item) {
|
||||||
|
if (item.deleted) return ['deleted', 'Deleted'];
|
||||||
|
if (item.loaded) return ['loaded', 'Loaded'];
|
||||||
|
if (item.enabled) return ['missing', 'Missing after reload'];
|
||||||
|
return ['disabled', 'Not in Orca'];
|
||||||
|
}
|
||||||
|
function setBusy(value) {
|
||||||
|
document.querySelectorAll('[data-toggle], [data-action]').forEach(control => control.disabled = value);
|
||||||
|
}
|
||||||
|
function render(message, error) {
|
||||||
|
$('profiles').innerHTML = items.map(item => {
|
||||||
|
const state = status(item);
|
||||||
|
const deleted = item.deleted ? ' disabled' : '';
|
||||||
|
const action = item.deleted
|
||||||
|
? '<button class="secondary" data-action="restore" data-id="' + esc(item.id) + '">Restore</button>'
|
||||||
|
: '<button data-action="delete" data-id="' + esc(item.id) + '">Delete</button>';
|
||||||
|
return '<section class="profile"><div class="top"><div><div class="name">' + esc(item.name) +
|
||||||
|
'</div><div class="kind">' + esc(item.kind) + '</div></div><span class="status ' + state[0] +
|
||||||
|
'">' + state[1] + '</span></div><div class="bottom"><label class="toggle"><input type="checkbox" data-toggle="' +
|
||||||
|
esc(item.id) + '"' + (item.enabled ? ' checked' : '') + deleted + '><span class="switch"></span>Available in Orca</label>' +
|
||||||
|
action + '</div></section>';
|
||||||
|
}).join('');
|
||||||
|
$('bundles').innerHTML = bundles.length ? bundles.map(id =>
|
||||||
|
'<div class="bundle"><code>' + esc(id) + '</code><button class="secondary" data-action="reload_bundle" data-bundle="' +
|
||||||
|
esc(id) + '">Reload</button></div>').join('') : '<div class="kind">No local bundles found.</div>';
|
||||||
|
$('message').textContent = error || message || '';
|
||||||
|
$('message').className = error ? 'message error' : 'message';
|
||||||
|
if (busy) setBusy(true);
|
||||||
|
}
|
||||||
|
function post(message) {
|
||||||
|
busy = true;
|
||||||
|
setBusy(true);
|
||||||
|
$('message').textContent = 'Reloading local bundle…';
|
||||||
|
$('message').className = 'message';
|
||||||
|
orca.postMessage(message);
|
||||||
|
}
|
||||||
|
$('close').addEventListener('click', () => orca.close());
|
||||||
|
$('profiles').addEventListener('change', event => {
|
||||||
|
if (!busy && event.target.matches('[data-toggle]')) {
|
||||||
|
post({command: 'set_enabled', id: event.target.dataset.toggle, enabled: event.target.checked});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('profiles').addEventListener('click', event => {
|
||||||
|
const button = event.target.closest('[data-action]');
|
||||||
|
if (!button || busy) return;
|
||||||
|
if (button.dataset.action === 'delete' && !confirm('Delete this demo profile from the local bundle?')) return;
|
||||||
|
post({command: button.dataset.action, id: button.dataset.id});
|
||||||
|
});
|
||||||
|
$('bundles').addEventListener('click', event => {
|
||||||
|
const button = event.target.closest('[data-action="reload_bundle"]');
|
||||||
|
if (button && !busy) post({command: 'reload_bundle', bundle_id: button.dataset.bundle});
|
||||||
|
});
|
||||||
|
orca.onMessage(message => {
|
||||||
|
if (!message || message.command !== 'state') return;
|
||||||
|
items = message.items || [];
|
||||||
|
bundles = message.bundles || [];
|
||||||
|
busy = false;
|
||||||
|
render(message.message, message.error);
|
||||||
|
});
|
||||||
|
orca.postMessage({command: 'state'});
|
||||||
|
</script></body></html>"""
|
||||||
|
|
||||||
|
|
||||||
|
class ReloadLocalBundleDemo(orca.script.ScriptPluginCapabilityBase):
|
||||||
|
window = None
|
||||||
|
|
||||||
|
def get_name(self):
|
||||||
|
return "Reload local bundle demo"
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
try:
|
||||||
|
if self.window is not None and self.window.is_open():
|
||||||
|
self._post_state()
|
||||||
|
return orca.ExecutionResult.success("Reload local bundle demo refreshed.")
|
||||||
|
self.window = orca.host.ui.create_window(
|
||||||
|
title="Reload local bundle demo", html=PAGE, width=500, height=420,
|
||||||
|
on_message=self.on_message, on_close=self.on_close,
|
||||||
|
)
|
||||||
|
return orca.ExecutionResult.success("Reload local bundle demo opened.")
|
||||||
|
except Exception as error:
|
||||||
|
message = "Reload local bundle demo failed: %s" % error
|
||||||
|
orca.host.ui.message(message, title="Reload local bundle demo", buttons="ok", icon="error")
|
||||||
|
return orca.ExecutionResult.failure(orca.PluginResult.RecoverableError, message)
|
||||||
|
|
||||||
|
def on_close(self):
|
||||||
|
self.window = None
|
||||||
|
|
||||||
|
def _post_state(self, message="", error=""):
|
||||||
|
try:
|
||||||
|
bundle = orca.host.preset_bundle()
|
||||||
|
payload = _state_payload(bundle, _read_state(bundle), message, error)
|
||||||
|
except Exception as state_error:
|
||||||
|
payload = {"command": "state", "items": [], "message": message, "error": str(state_error)}
|
||||||
|
if self.window is not None and self.window.is_open():
|
||||||
|
self.window.post(payload)
|
||||||
|
|
||||||
|
def on_message(self, message):
|
||||||
|
try:
|
||||||
|
message = message or {}
|
||||||
|
bundle = orca.host.preset_bundle()
|
||||||
|
state = _read_state(bundle)
|
||||||
|
if message.get("command") == "state":
|
||||||
|
self._post_state()
|
||||||
|
return
|
||||||
|
if message.get("command") == "reload_bundle":
|
||||||
|
bundle_id = message.get("bundle_id")
|
||||||
|
if not isinstance(bundle_id, str) or not bundle_id:
|
||||||
|
raise RuntimeError("A bundle id is required")
|
||||||
|
orca.host.preset.bundles.reload_local(bundle_id)
|
||||||
|
self._post_state(message="Reloaded local bundle %s." % bundle_id)
|
||||||
|
return
|
||||||
|
item = next((item for item in DEMO_PRESETS if item["id"] == message.get("id")), None)
|
||||||
|
if item is None:
|
||||||
|
raise RuntimeError("Unknown demo preset")
|
||||||
|
profile = _profile_state(state, item)
|
||||||
|
command = message.get("command")
|
||||||
|
if command == "set_enabled":
|
||||||
|
enabled = message.get("enabled")
|
||||||
|
if not isinstance(enabled, bool):
|
||||||
|
raise RuntimeError("Preset availability must be a boolean")
|
||||||
|
if profile["deleted"]:
|
||||||
|
raise RuntimeError("Restore the deleted demo preset before enabling it")
|
||||||
|
profile["enabled"] = enabled
|
||||||
|
elif command == "delete":
|
||||||
|
profile.update({"enabled": False, "deleted": True, "parent_name": ""})
|
||||||
|
elif command == "restore":
|
||||||
|
profile.update({"enabled": False, "deleted": False, "parent_name": ""})
|
||||||
|
else:
|
||||||
|
raise RuntimeError("Unknown demo action")
|
||||||
|
_reconcile(bundle, state)
|
||||||
|
self._post_state(message="The change was applied. Check the corresponding preset selector in Orca.")
|
||||||
|
except Exception as error:
|
||||||
|
self._post_state(error=str(error))
|
||||||
|
|
||||||
|
|
||||||
|
@orca.plugin
|
||||||
|
class ReloadLocalBundleDemoPackage(orca.base):
|
||||||
|
def register_capabilities(self):
|
||||||
|
orca.register_capability(ReloadLocalBundleDemo)
|
||||||
@@ -1374,6 +1374,26 @@ bool AppConfig::write_config_file(const std::string &path, std::string body, con
|
|||||||
return true;
|
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
|
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);
|
const auto it_v = m_vendors.find(vendor);
|
||||||
|
|||||||
@@ -306,6 +306,8 @@ 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; }
|
const std::vector<std::string> &get_filament_presets() const { return m_filament_presets; }
|
||||||
void set_filament_presets(const std::vector<std::string> &filament_presets){
|
void set_filament_presets(const std::vector<std::string> &filament_presets){
|
||||||
m_filament_presets = filament_presets;
|
m_filament_presets = filament_presets;
|
||||||
|
|||||||
@@ -9776,7 +9776,10 @@ bool has_restore_data(std::string & path, std::string& origin)
|
|||||||
origin = "<lock>";
|
origin = "<lock>";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (boost::filesystem::exists(path + "/lock.txt")) {
|
// 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)) {
|
||||||
std::string pid;
|
std::string pid;
|
||||||
load_string_file(path + "/lock.txt", pid);
|
load_string_file(path + "/lock.txt", pid);
|
||||||
try {
|
try {
|
||||||
@@ -9791,10 +9794,10 @@ bool has_restore_data(std::string & path, std::string& origin)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::string file3mf = path + "/.3mf";
|
std::string file3mf = path + "/.3mf";
|
||||||
if (!boost::filesystem::exists(file3mf))
|
if (!boost::filesystem::exists(file3mf, ec))
|
||||||
return false;
|
return false;
|
||||||
try {
|
try {
|
||||||
if (boost::filesystem::exists(path + "/origin.txt"))
|
if (boost::filesystem::exists(path + "/origin.txt", ec))
|
||||||
load_string_file(path + "/origin.txt", origin);
|
load_string_file(path + "/origin.txt", origin);
|
||||||
}
|
}
|
||||||
catch (...) {
|
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, and the
|
// Records one extraction for the warnings: the type of a modifier with a discarded fragment, the
|
||||||
// evaluated/reached flags for "had no effect" (a discarded fragment counts as reached, a contact not).
|
// 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,
|
static void record_extraction(PreciseSeamWarnings *warnings, const ModelVolume *modifier,
|
||||||
const SegmentExtraction &extracted)
|
const SegmentExtraction &extracted)
|
||||||
@@ -685,13 +685,10 @@ static void record_extraction(PreciseSeamWarnings *warnings, const ModelVolume *
|
|||||||
const auto it = warnings->modifier_usage.find(modifier);
|
const auto it = warnings->modifier_usage.find(modifier);
|
||||||
if (it == warnings->modifier_usage.end())
|
if (it == warnings->modifier_usage.end())
|
||||||
return;
|
return;
|
||||||
// Load before store: most calls find the flag already set, so shared cache lines stay clean.
|
|
||||||
PreciseSeamWarnings::ModifierUsage &usage = it->second;
|
PreciseSeamWarnings::ModifierUsage &usage = it->second;
|
||||||
if (!usage.checked.load(std::memory_order_relaxed))
|
PreciseSeamWarnings::set(usage.checked);
|
||||||
usage.checked.store(true, std::memory_order_relaxed);
|
if (!extracted.segments.empty() || extracted.full_containment || extracted.discarded_fragments > 0)
|
||||||
const bool reached = !extracted.segments.empty() || extracted.full_containment || extracted.discarded_fragments > 0;
|
PreciseSeamWarnings::set(usage.reached);
|
||||||
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.
|
// Strong priority is per modifier, never a global maximum across different modifiers.
|
||||||
@@ -953,22 +950,15 @@ std::vector<WeakModifierSegment> collect_weak_modifier_segments(
|
|||||||
const SegmentExtraction extracted = extract_perimeter_segments(prepared, it->second[layer_id], modifier_volume->type(), context);
|
const SegmentExtraction extracted = extract_perimeter_segments(prepared, it->second[layer_id], modifier_volume->type(), context);
|
||||||
record_extraction(warnings, modifier_volume, extracted);
|
record_extraction(warnings, modifier_volume, extracted);
|
||||||
const auto type = convert_weak_modifier_type(modifier_volume->type());
|
const auto type = convert_weak_modifier_type(modifier_volume->type());
|
||||||
// Full containment works like seam painting: Enforced and Neutral type the whole perimeter;
|
// Full containment works like seam painting all round: the whole perimeter takes the type.
|
||||||
// Blocked is skipped with a warning, since the seam cannot avoid the whole perimeter.
|
// A perimeter left whole Blocked also gets a warning (see apply_weak_modifiers_to_perimeter()).
|
||||||
if (extracted.full_containment) {
|
if (extracted.full_containment) {
|
||||||
if (type == EnforcedBlockedSeamPoint::Blocked) {
|
result.push_back({type, Point(), PerimeterPosition{0, 0.}, Point(), PerimeterPosition{0, 0.}, true, modifier_volume});
|
||||||
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;
|
continue;
|
||||||
}
|
}
|
||||||
for (const PerimeterSegment &segment : extracted.segments) {
|
for (const PerimeterSegment &segment : extracted.segments) {
|
||||||
result.push_back({type, segment.polyline.points.front(), segment.begin,
|
result.push_back({type, segment.polyline.points.front(), segment.begin,
|
||||||
segment.polyline.points.back(), segment.end});
|
segment.polyline.points.back(), segment.end, false, modifier_volume});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Boundary insertion, refinement and last-write-wins priority use the established path.
|
// Boundary insertion, refinement and last-write-wins priority use the established path.
|
||||||
@@ -979,7 +969,8 @@ void apply_weak_modifiers_to_perimeter(
|
|||||||
const std::vector<WeakModifierSegment> &weak_segments,
|
const std::vector<WeakModifierSegment> &weak_segments,
|
||||||
PrintObjectSeamData::LayerSeams &result,
|
PrintObjectSeamData::LayerSeams &result,
|
||||||
const SeamPlacerImpl::Perimeter &perimeter,
|
const SeamPlacerImpl::Perimeter &perimeter,
|
||||||
bool &some_point_enforced)
|
bool &some_point_enforced,
|
||||||
|
PreciseSeamWarnings *warnings)
|
||||||
{
|
{
|
||||||
// Get z-coordinate for unscaling boundary points
|
// Get z-coordinate for unscaling boundary points
|
||||||
const float z_coord = result.points[perimeter.start_index].position.z();
|
const float z_coord = result.points[perimeter.start_index].position.z();
|
||||||
@@ -1029,6 +1020,19 @@ void apply_weak_modifiers_to_perimeter(
|
|||||||
some_point_enforced = true;
|
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
|
// Restore precise seam positions that may have been modified
|
||||||
|
|||||||
@@ -49,18 +49,20 @@ struct PreciseSeamWarnings {
|
|||||||
// Masks of the Precise Seam types that caused each warning reason, one bit per type (type_bit()).
|
// 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.
|
// 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> 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, Blocked.
|
std::atomic<unsigned> full_containment{0}; // Skipped for a perimeter fully inside: Center/Left/Right.
|
||||||
std::atomic<unsigned> failed_types{0}; // Types with at least one discarded fragment.
|
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.
|
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.
|
// 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.
|
// Clipper is deterministic, so a prismatic model can repeat the same case on every layer.
|
||||||
std::atomic<size_t> recovered_fragments{0};
|
std::atomic<size_t> recovered_fragments{0};
|
||||||
|
|
||||||
// Per-modifier flags for the "had no effect" warning. Modifiers are registered before the parallel
|
// Per-modifier flags for the "had no effect" and "covers an entire perimeter" warnings. Modifiers are
|
||||||
// phase, so workers only set flags; unregistered ones (e.g. in tests) are not tracked.
|
// registered before the parallel phase, so workers only set flags; unregistered ones (e.g. in tests)
|
||||||
|
// are not tracked.
|
||||||
struct ModifierUsage {
|
struct ModifierUsage {
|
||||||
std::atomic<bool> checked{false}; // Extracted on at least one perimeter.
|
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> 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::unordered_map<const ModelVolume*, ModifierUsage> modifier_usage;
|
std::unordered_map<const ModelVolume*, ModifierUsage> modifier_usage;
|
||||||
|
|
||||||
@@ -70,13 +72,18 @@ struct PreciseSeamWarnings {
|
|||||||
assert(is_precise_seam(type));
|
assert(is_precise_seam(type));
|
||||||
return 1u << (int(type) - int(ModelVolumeType::PRECISE_SEAM_CENTER));
|
return 1u << (int(type) - int(ModelVolumeType::PRECISE_SEAM_CENTER));
|
||||||
}
|
}
|
||||||
// Load before fetch_or: most calls find the bit already set, so shared cache lines stay clean.
|
// Load before writing: most calls find the bit or flag already set, so shared cache lines stay clean.
|
||||||
static void mark(std::atomic<unsigned> &mask, ModelVolumeType type)
|
static void mark(std::atomic<unsigned> &mask, ModelVolumeType type)
|
||||||
{
|
{
|
||||||
const unsigned bit = type_bit(type);
|
const unsigned bit = type_bit(type);
|
||||||
if ((mask.load(std::memory_order_relaxed) & bit) == 0)
|
if ((mask.load(std::memory_order_relaxed) & bit) == 0)
|
||||||
mask.fetch_or(bit, std::memory_order_relaxed);
|
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.
|
// Optional caller identity for concise diagnostics when an intersection is discarded.
|
||||||
@@ -139,9 +146,10 @@ struct WeakModifierSegment {
|
|||||||
PerimeterPosition left_position; // Position on the source perimeter before insertion/refinement.
|
PerimeterPosition left_position; // Position on the source perimeter before insertion/refinement.
|
||||||
Point right_point; // Coordinates of right (last) point of segment
|
Point right_point; // Coordinates of right (last) point of segment
|
||||||
PerimeterPosition right_position; // Retained provenance, not an index into the modified polygon.
|
PerimeterPosition right_position; // Retained provenance, not an index into the modified polygon.
|
||||||
// Full containment of an Enforced or Neutral modifier: the zone is the whole perimeter, without
|
// Full containment of a weak modifier: the zone is the whole perimeter, without boundaries
|
||||||
// boundaries (the points and positions above are unused and nothing is inserted for it).
|
// (the points and positions above are unused and nothing is inserted for it).
|
||||||
bool whole_perimeter = false;
|
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
|
// Collects the object's Precise Seam volumes: strong ones in priority order, weak ones in application
|
||||||
@@ -173,12 +181,14 @@ std::vector<WeakModifierSegment> collect_weak_modifier_segments(
|
|||||||
PreciseSeamWarnings* warnings = nullptr);
|
PreciseSeamWarnings* warnings = nullptr);
|
||||||
|
|
||||||
// Retypes the candidates inside each zone in the given order (pass zones lowest priority first);
|
// Retypes the candidates inside each zone in the given order (pass zones lowest priority first);
|
||||||
// sets some_point_enforced when an Enforced zone applies.
|
// 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.
|
||||||
void apply_weak_modifiers_to_perimeter(
|
void apply_weak_modifiers_to_perimeter(
|
||||||
const std::vector<WeakModifierSegment> &weak_segments,
|
const std::vector<WeakModifierSegment> &weak_segments,
|
||||||
PrintObjectSeamData::LayerSeams &result,
|
PrintObjectSeamData::LayerSeams &result,
|
||||||
const SeamPlacerImpl::Perimeter &perimeter,
|
const SeamPlacerImpl::Perimeter &perimeter,
|
||||||
bool &some_point_enforced);
|
bool &some_point_enforced,
|
||||||
|
PreciseSeamWarnings *warnings = nullptr);
|
||||||
|
|
||||||
// Restore precise seam positions that may have been modified by alignment
|
// Restore precise seam positions that may have been modified by alignment
|
||||||
// Iterates through all perimeters and restores precise_seam_point positions
|
// 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
|
// Apply weak modifiers if no strong modifier was inserted
|
||||||
if (!inserted_seam_position.has_value() && !weak_segments.empty()) {
|
if (!inserted_seam_position.has_value() && !weak_segments.empty()) {
|
||||||
PreciseSeam::apply_weak_modifiers_to_perimeter(
|
PreciseSeam::apply_weak_modifiers_to_perimeter(
|
||||||
weak_segments, result, perimeter, some_point_enforced);
|
weak_segments, result, perimeter, some_point_enforced, warnings);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (some_point_enforced) {
|
if (some_point_enforced) {
|
||||||
@@ -896,18 +896,15 @@ struct SeamComparator {
|
|||||||
return a.central_enforcer;
|
return a.central_enforcer;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.type == EnforcedBlockedSeamPoint::Enforced) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.type == EnforcedBlockedSeamPoint::Blocked) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.type != b.type) {
|
if (a.type != b.type) {
|
||||||
return 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;
|
||||||
|
}
|
||||||
|
|
||||||
//avoid overhangs
|
//avoid overhangs
|
||||||
if ((a.overhang > 0.0f || b.overhang > 0.0f)
|
if ((a.overhang > 0.0f || b.overhang > 0.0f)
|
||||||
&& abs(a.overhang - b.overhang) > (0.1f * a.perimeter.flow_width)) {
|
&& abs(a.overhang - b.overhang) > (0.1f * a.perimeter.flow_width)) {
|
||||||
@@ -1660,18 +1657,42 @@ 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());
|
parts.push_back((boost::format(_u8L("multiple intersections with a perimeter, only one was used (%1%)")) % type_list(mi)).str());
|
||||||
if (fc != 0)
|
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());
|
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 evaluated somewhere that never reached a perimeter; never-evaluated ones are not reported.
|
// Modifiers whose usage flags match, in print and volume order, so the named one is deterministic.
|
||||||
// Print and volume order make the named one deterministic; the log lists them all.
|
const auto collect_modifiers = [&print, &precise_seam_warnings](const auto &matches) {
|
||||||
std::vector<const ModelVolume*> no_effect;
|
std::vector<const ModelVolume*> found;
|
||||||
for (const PrintObject *po : print.objects())
|
for (const PrintObject *po : print.objects())
|
||||||
for (const ModelVolume *volume : po->model_object()->volumes) {
|
for (const ModelVolume *volume : po->model_object()->volumes) {
|
||||||
const auto it = precise_seam_warnings.modifier_usage.find(volume);
|
const auto it = precise_seam_warnings.modifier_usage.find(volume);
|
||||||
if (it != precise_seam_warnings.modifier_usage.end() &&
|
if (it != precise_seam_warnings.modifier_usage.end() && matches(it->second) &&
|
||||||
it->second.checked.load(std::memory_order_relaxed) &&
|
std::find(found.begin(), found.end(), volume) == found.end())
|
||||||
!it->second.reached.load(std::memory_order_relaxed) &&
|
found.push_back(volume);
|
||||||
std::find(no_effect.begin(), no_effect.end(), volume) == no_effect.end())
|
}
|
||||||
no_effect.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);
|
||||||
|
});
|
||||||
// The user warning names only the first one; the log lists them all.
|
// The user warning names only the first one; the log lists them all.
|
||||||
for (const ModelVolume *volume : no_effect)
|
for (const ModelVolume *volume : no_effect)
|
||||||
BOOST_LOG_TRIVIAL(warning) << "[PreciseSeamNoEffect] object=\"" << volume->get_object()->name
|
BOOST_LOG_TRIVIAL(warning) << "[PreciseSeamNoEffect] object=\"" << volume->get_object()->name
|
||||||
|
|||||||
@@ -1822,7 +1822,7 @@ PresetCollection::UserPresetLoad PresetCollection::resolve_user_preset(
|
|||||||
const boost::filesystem::path &file, const std::string &canonical_name,
|
const boost::filesystem::path &file, const std::string &canonical_name,
|
||||||
const PresetOrigin &load_origin, ForwardCompatibilitySubstitutionRule substitution_rule,
|
const PresetOrigin &load_origin, ForwardCompatibilitySubstitutionRule substitution_rule,
|
||||||
const std::string &extruder_id_name, const std::string &extruder_variant_name,
|
const std::string &extruder_id_name, const std::string &extruder_variant_name,
|
||||||
std::set<std::string> *key_set1, std::set<std::string> *key_set2) const
|
std::set<std::string> *key_set1, std::set<std::string> *key_set2, bool read_only) const
|
||||||
{
|
{
|
||||||
UserPresetLoad out;
|
UserPresetLoad out;
|
||||||
out.preset = Preset(m_type, canonical_name, false);
|
out.preset = Preset(m_type, canonical_name, false);
|
||||||
@@ -1856,7 +1856,10 @@ PresetCollection::UserPresetLoad PresetCollection::resolve_user_preset(
|
|||||||
|
|
||||||
std::string version_str = key_values[BBL_JSON_KEY_VERSION];
|
std::string version_str = key_values[BBL_JSON_KEY_VERSION];
|
||||||
boost::optional<Semver> version = Semver::parse(version_str);
|
boost::optional<Semver> version = Semver::parse(version_str);
|
||||||
if (!version) return out;
|
if (!version) {
|
||||||
|
out.errors.push_back((boost::format("invalid version %1% in config %2%") % version_str % preset.file).str());
|
||||||
|
return out;
|
||||||
|
}
|
||||||
preset.version = *version;
|
preset.version = *version;
|
||||||
|
|
||||||
if (key_values.find(BBL_JSON_KEY_FILAMENT_ID) != key_values.end())
|
if (key_values.find(BBL_JSON_KEY_FILAMENT_ID) != key_values.end())
|
||||||
@@ -1878,6 +1881,12 @@ PresetCollection::UserPresetLoad PresetCollection::resolve_user_preset(
|
|||||||
inherit_preset = this->find_preset2(inherits_value);
|
inherit_preset = this->find_preset2(inherits_value);
|
||||||
Preset::normalize_inherits(config, inherit_preset);
|
Preset::normalize_inherits(config, inherit_preset);
|
||||||
}
|
}
|
||||||
|
// Local hot reload only supports FFF; no SLA default printer is installed.
|
||||||
|
if (read_only && load_origin.kind == PresetOrigin::Kind::LocalBundle && m_type == Preset::TYPE_PRINTER &&
|
||||||
|
Preset::printer_technology(config) != ptFFF) {
|
||||||
|
out.errors.push_back((boost::format("local bundle printer %1% is not FFF") % preset.file).str());
|
||||||
|
return out;
|
||||||
|
}
|
||||||
const Preset& default_preset = this->default_preset_for(config);
|
const Preset& default_preset = this->default_preset_for(config);
|
||||||
if (inherit_preset) {
|
if (inherit_preset) {
|
||||||
preset.config = inherit_preset->config;
|
preset.config = inherit_preset->config;
|
||||||
@@ -2042,7 +2051,7 @@ void PresetCollection::load_presets(
|
|||||||
resolve_then_commit<CNumericLocalesSetter>(files.size(),
|
resolve_then_commit<CNumericLocalesSetter>(files.size(),
|
||||||
[&](size_t i) {
|
[&](size_t i) {
|
||||||
return this->resolve_user_preset(files[i].path, files[i].canonical_name, resolved_origin, substitution_rule,
|
return this->resolve_user_preset(files[i].path, files[i].canonical_name, resolved_origin, substitution_rule,
|
||||||
extruder_id_name, extruder_variant_name, key_set1, key_set2);
|
extruder_id_name, extruder_variant_name, key_set1, key_set2, read_only);
|
||||||
},
|
},
|
||||||
[&](size_t i, UserPresetLoad &&loaded) {
|
[&](size_t i, UserPresetLoad &&loaded) {
|
||||||
// Resolve read the files without the lock, so another instance may have saved
|
// Resolve read the files without the lock, so another instance may have saved
|
||||||
@@ -2062,7 +2071,7 @@ void PresetCollection::load_presets(
|
|||||||
if (! (on_disk == loaded.on_disk)) {
|
if (! (on_disk == loaded.on_disk)) {
|
||||||
CNumericLocalesSetter locales_setter;
|
CNumericLocalesSetter locales_setter;
|
||||||
loaded = this->resolve_user_preset(files[i].path, files[i].canonical_name, resolved_origin, substitution_rule,
|
loaded = this->resolve_user_preset(files[i].path, files[i].canonical_name, resolved_origin, substitution_rule,
|
||||||
extruder_id_name, extruder_variant_name, key_set1, key_set2);
|
extruder_id_name, extruder_variant_name, key_set1, key_set2, read_only);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const bool leave_files = read_only || (! lock_path.empty() && ! instance_lock.locked());
|
const bool leave_files = read_only || (! lock_path.empty() && ! instance_lock.locked());
|
||||||
@@ -3135,7 +3144,9 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
|
|||||||
}
|
}
|
||||||
// Insert a new profile.
|
// Insert a new profile.
|
||||||
//BBS: add project embedded preset logic
|
//BBS: add project embedded preset logic
|
||||||
bool from_project = boost::algorithm::iends_with(name, ".3mf");
|
// A G-code's settings are shown like a project's, so a preset made from them lives with the project
|
||||||
|
// rather than in the user's library.
|
||||||
|
bool from_project = boost::algorithm::iends_with(name, ".3mf") || is_gcode_file(name);
|
||||||
if (m_type == Preset::TYPE_PRINT)
|
if (m_type == Preset::TYPE_PRINT)
|
||||||
cfg.option<ConfigOptionString >("print_settings_id", true)->value = new_name;
|
cfg.option<ConfigOptionString >("print_settings_id", true)->value = new_name;
|
||||||
else if (m_type == Preset::TYPE_FILAMENT)
|
else if (m_type == Preset::TYPE_FILAMENT)
|
||||||
|
|||||||
@@ -991,7 +991,8 @@ private:
|
|||||||
UserPresetLoad resolve_user_preset(const boost::filesystem::path &file, const std::string &canonical_name,
|
UserPresetLoad resolve_user_preset(const boost::filesystem::path &file, const std::string &canonical_name,
|
||||||
const PresetOrigin &load_origin, ForwardCompatibilitySubstitutionRule substitution_rule,
|
const PresetOrigin &load_origin, ForwardCompatibilitySubstitutionRule substitution_rule,
|
||||||
const std::string &extruder_id_name, const std::string &extruder_variant_name,
|
const std::string &extruder_id_name, const std::string &extruder_variant_name,
|
||||||
std::set<std::string> *key_set1, std::set<std::string> *key_set2) const;
|
std::set<std::string> *key_set1, std::set<std::string> *key_set2,
|
||||||
|
bool read_only) const;
|
||||||
|
|
||||||
// Install one resolved preset. The collection, its alias maps, the error count
|
// Install one resolved preset. The collection, its alias maps, the error count
|
||||||
// and the preset files on disk are touched here and only here.
|
// and the preset files on disk are touched here and only here.
|
||||||
|
|||||||
+434
-29
@@ -1,3 +1,4 @@
|
|||||||
|
#include <array>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <boost/filesystem/path.hpp>
|
#include <boost/filesystem/path.hpp>
|
||||||
#include <boost/filesystem/operations.hpp>
|
#include <boost/filesystem/operations.hpp>
|
||||||
@@ -56,8 +57,10 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <mutex>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <shared_mutex>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
@@ -1383,6 +1386,27 @@ std::string PresetBundle::get_hotend_model_for_printer_model(std::string model_n
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void load_bundle_presets(PresetBundle& bundle, const std::string& bundle_dir, BundleMetadata& metadata,
|
||||||
|
const PresetOrigin& origin, PresetsConfigSubstitutions& substitutions,
|
||||||
|
ForwardCompatibilitySubstitutionRule substitution_rule, bool read_only = false)
|
||||||
|
{
|
||||||
|
struct Collection {
|
||||||
|
PresetCollection& presets;
|
||||||
|
const char* directory;
|
||||||
|
std::vector<std::string>& names;
|
||||||
|
};
|
||||||
|
|
||||||
|
metadata.print_presets.clear();
|
||||||
|
metadata.filament_presets.clear();
|
||||||
|
metadata.printer_presets.clear();
|
||||||
|
for (Collection collection : {
|
||||||
|
Collection{ bundle.prints, PRESET_PRINT_NAME, metadata.print_presets },
|
||||||
|
Collection{ bundle.filaments, PRESET_FILAMENT_NAME, metadata.filament_presets },
|
||||||
|
Collection{ bundle.printers, PRESET_PRINTER_NAME, metadata.printer_presets } })
|
||||||
|
collection.presets.load_presets(bundle_dir, collection.directory, substitutions, substitution_rule,
|
||||||
|
[&](Preset& preset) { collection.names.push_back(preset.name); }, origin, read_only);
|
||||||
|
}
|
||||||
|
|
||||||
PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, ForwardCompatibilitySubstitutionRule substitution_rule, bool read_only)
|
PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, ForwardCompatibilitySubstitutionRule substitution_rule, bool read_only)
|
||||||
{
|
{
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " entry and user is: " << user;
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " entry and user is: " << user;
|
||||||
@@ -1431,19 +1455,8 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
|
|||||||
|
|
||||||
BundleMetadata metadata;
|
BundleMetadata metadata;
|
||||||
if (!load_bundle_metadata(metadata_file, metadata)) continue;
|
if (!load_bundle_metadata(metadata_file, metadata)) continue;
|
||||||
metadata.print_presets.clear();
|
load_bundle_presets(*this, bundle_dir, metadata, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id),
|
||||||
metadata.filament_presets.clear();
|
substitutions, substitution_rule, read_only);
|
||||||
metadata.printer_presets.clear();
|
|
||||||
|
|
||||||
this->prints.load_presets(bundle_dir, PRESET_PRINT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
|
||||||
metadata.print_presets.push_back(preset.name);
|
|
||||||
}, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id), read_only);
|
|
||||||
this->filaments.load_presets(bundle_dir, PRESET_FILAMENT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
|
||||||
metadata.filament_presets.push_back(preset.name);
|
|
||||||
}, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id), read_only);
|
|
||||||
this->printers.load_presets(bundle_dir, PRESET_PRINTER_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
|
||||||
metadata.printer_presets.push_back(preset.name);
|
|
||||||
}, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id), read_only);
|
|
||||||
metadata.bundle_type = BundleType::Local;
|
metadata.bundle_type = BundleType::Local;
|
||||||
metadata.path = metadata_file.string();
|
metadata.path = metadata_file.string();
|
||||||
|
|
||||||
@@ -1466,20 +1479,10 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
|
|||||||
|
|
||||||
BundleMetadata metadata;
|
BundleMetadata metadata;
|
||||||
if (!load_bundle_metadata(metadata_file, metadata)) continue;
|
if (!load_bundle_metadata(metadata_file, metadata)) continue;
|
||||||
metadata.print_presets.clear();
|
|
||||||
metadata.filament_presets.clear();
|
|
||||||
metadata.printer_presets.clear();
|
|
||||||
metadata.is_subscribed = true;
|
metadata.is_subscribed = true;
|
||||||
|
|
||||||
this->prints.load_presets(bundle_dir, PRESET_PRINT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
load_bundle_presets(*this, bundle_dir, metadata, PresetOrigin(PresetOrigin::Kind::SubscribedBundle, metadata.id),
|
||||||
metadata.print_presets.push_back(preset.name);
|
substitutions, substitution_rule, read_only);
|
||||||
}, PresetOrigin(PresetOrigin::Kind::SubscribedBundle, metadata.id), read_only);
|
|
||||||
this->filaments.load_presets(bundle_dir, PRESET_FILAMENT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
|
||||||
metadata.filament_presets.push_back(preset.name);
|
|
||||||
}, PresetOrigin(PresetOrigin::Kind::SubscribedBundle, metadata.id), read_only);
|
|
||||||
this->printers.load_presets(bundle_dir, PRESET_PRINTER_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
|
||||||
metadata.printer_presets.push_back(preset.name);
|
|
||||||
}, PresetOrigin(PresetOrigin::Kind::SubscribedBundle, metadata.id), read_only);
|
|
||||||
|
|
||||||
metadata.bundle_type = BundleType::Subscribed;
|
metadata.bundle_type = BundleType::Subscribed;
|
||||||
metadata.path = metadata_file.string();
|
metadata.path = metadata_file.string();
|
||||||
@@ -1534,6 +1537,335 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
|
|||||||
return PresetsConfigSubstitutions();
|
return PresetsConfigSubstitutions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool is_single_path_component(const std::string& value)
|
||||||
|
{
|
||||||
|
const fs::path path(value);
|
||||||
|
return !value.empty() && value != "." && value != ".." && value.find_first_of("/\\") == std::string::npos &&
|
||||||
|
!path.has_root_name() && !path.has_root_directory();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool check_local_bundle(PresetBundleMetadata& bundles, const fs::path& user_dir, const std::string& bundle_id,
|
||||||
|
BundleMetadata& metadata, std::string& error)
|
||||||
|
{
|
||||||
|
if (!is_single_path_component(bundle_id)) {
|
||||||
|
error = "Local bundle id must be a single path component";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const fs::path bundle_dir = user_dir / PRESET_LOCAL_DIR / bundle_id;
|
||||||
|
if (!fs::is_directory(bundle_dir)) {
|
||||||
|
error = "Local bundle directory does not exist";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (fs::exists(user_dir / PRESET_SUBSCRIBED_DIR / bundle_id)) {
|
||||||
|
error = "A subscribed bundle already uses this id";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::shared_lock<std::shared_mutex> lock(bundles.RWMtx);
|
||||||
|
const auto it = bundles.m_bundles.find(bundle_id);
|
||||||
|
if (it != bundles.m_bundles.end() && (it->second.bundle_type == BundleType::Subscribed || it->second.is_subscribed)) {
|
||||||
|
error = "A subscribed bundle already uses this id";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!metadata.load_from_json((bundle_dir / PRESET_BUNDLE_METADATA).string()) || metadata.id != bundle_id) {
|
||||||
|
error = "Local bundle metadata is missing, malformed, or has a different id";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> PresetBundle::list_local_bundle_ids(const std::string& preset_folder)
|
||||||
|
{
|
||||||
|
std::vector<std::string> ids;
|
||||||
|
const std::string user = preset_folder.empty() ? DEFAULT_USER_FOLDER_NAME : preset_folder;
|
||||||
|
if (!is_single_path_component(user))
|
||||||
|
return ids;
|
||||||
|
|
||||||
|
const fs::path user_dir = fs::path(data_dir()) / PRESET_USER_DIR / user;
|
||||||
|
const fs::path local_dir = user_dir / PRESET_LOCAL_DIR;
|
||||||
|
try {
|
||||||
|
if (!fs::is_directory(local_dir))
|
||||||
|
return ids;
|
||||||
|
for (const auto& entry : fs::directory_iterator(local_dir)) {
|
||||||
|
const std::string bundle_id = entry.path().filename().string();
|
||||||
|
try {
|
||||||
|
BundleMetadata metadata;
|
||||||
|
std::string error;
|
||||||
|
if (check_local_bundle(bundles, user_dir, bundle_id, metadata, error))
|
||||||
|
ids.push_back(bundle_id);
|
||||||
|
} catch (const std::exception& exception) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": skipping " << bundle_id << ": " << exception.what();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (const std::exception& exception) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": " << exception.what();
|
||||||
|
}
|
||||||
|
std::sort(ids.begin(), ids.end());
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PresetBundle::reload_local_bundle(const std::string& preset_folder, const std::string& bundle_id, std::string* error)
|
||||||
|
{
|
||||||
|
auto fail = [error](const std::string& message) {
|
||||||
|
if (error != nullptr)
|
||||||
|
*error = message;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if (!is_single_path_component(bundle_id))
|
||||||
|
return fail("Local bundle id must be a single path component");
|
||||||
|
|
||||||
|
const std::string user = preset_folder.empty() ? DEFAULT_USER_FOLDER_NAME : preset_folder;
|
||||||
|
if (!is_single_path_component(user))
|
||||||
|
return fail("Preset folder must be a single path component");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fs::path user_dir = fs::path(data_dir()) / PRESET_USER_DIR / user;
|
||||||
|
const fs::path bundle_dir = user_dir / PRESET_LOCAL_DIR / bundle_id;
|
||||||
|
const fs::path metadata_file = bundle_dir / PRESET_BUNDLE_METADATA;
|
||||||
|
|
||||||
|
BundleMetadata metadata;
|
||||||
|
std::string check_error;
|
||||||
|
if (!check_local_bundle(bundles, user_dir, bundle_id, metadata, check_error))
|
||||||
|
return fail(check_error);
|
||||||
|
|
||||||
|
// Keep background preset sync from changing the collections between copying and committing them.
|
||||||
|
std::scoped_lock collection_lock(prints.m_mutex, filaments.m_mutex, printers.m_mutex);
|
||||||
|
const std::string target_prefix = std::string(PRESET_LOCAL_DIR) + "/" + bundle_id + "/";
|
||||||
|
const auto is_target = [&](const Preset& preset) {
|
||||||
|
return preset.bundle_id == bundle_id && boost::starts_with(preset.name, target_prefix);
|
||||||
|
};
|
||||||
|
struct Selection {
|
||||||
|
std::string name;
|
||||||
|
Preset base{Preset::TYPE_INVALID, std::string()};
|
||||||
|
Preset edited{Preset::TYPE_INVALID, std::string()};
|
||||||
|
std::vector<std::string> dirty_options;
|
||||||
|
};
|
||||||
|
auto snapshot = [&](const PresetCollection& collection, Selection& selection) {
|
||||||
|
selection.name = collection.get_selected_preset_name();
|
||||||
|
if (selection.name.empty())
|
||||||
|
return false;
|
||||||
|
for (const Preset& preset : collection.get_presets()) {
|
||||||
|
if (preset.name == selection.name) {
|
||||||
|
selection.base = preset;
|
||||||
|
selection.edited = collection.get_edited_preset();
|
||||||
|
selection.dirty_options = PresetCollection::dirty_options(&selection.edited, &selection.base);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
Selection print_selection, filament_selection, printer_selection;
|
||||||
|
if (!snapshot(prints, print_selection) || !snapshot(filaments, filament_selection) || !snapshot(printers, printer_selection))
|
||||||
|
return fail("Preset selections are not initialized");
|
||||||
|
|
||||||
|
PresetBundle staged;
|
||||||
|
// Collection copies keep vendor pointers into the live vendor map, which is not replaced by this reload.
|
||||||
|
staged.prints = prints;
|
||||||
|
staged.filaments = filaments;
|
||||||
|
staged.printers = printers;
|
||||||
|
staged.filament_presets = filament_presets;
|
||||||
|
staged.project_config.apply_only(project_config, {"flush_volumes_matrix", "flush_volumes_vector", "flush_multiplier"});
|
||||||
|
auto remove_target = [&](PresetCollection& collection) {
|
||||||
|
auto begin = collection.m_presets.begin() + collection.m_num_default_presets;
|
||||||
|
collection.m_presets.erase(std::remove_if(begin, collection.m_presets.end(), is_target), collection.m_presets.end());
|
||||||
|
collection.sort_presets();
|
||||||
|
};
|
||||||
|
remove_target(staged.prints);
|
||||||
|
remove_target(staged.filaments);
|
||||||
|
remove_target(staged.printers);
|
||||||
|
|
||||||
|
const std::array<int, 3> errors_before = { staged.prints.error_count(), staged.filaments.error_count(), staged.printers.error_count() };
|
||||||
|
PresetsConfigSubstitutions substitutions;
|
||||||
|
load_bundle_presets(staged, bundle_dir.string(), metadata, PresetOrigin(PresetOrigin::Kind::LocalBundle, bundle_id),
|
||||||
|
substitutions, ForwardCompatibilitySubstitutionRule::Enable, true);
|
||||||
|
if (staged.prints.error_count() != errors_before[0] || staged.filaments.error_count() != errors_before[1] || staged.printers.error_count() != errors_before[2])
|
||||||
|
return fail("Local bundle contains an invalid preset");
|
||||||
|
|
||||||
|
auto dirty_target_was_removed = [&](const Selection& selection, const PresetCollection& collection) {
|
||||||
|
if (selection.dirty_options.empty() || !is_target(selection.base))
|
||||||
|
return false;
|
||||||
|
for (const Preset& preset : collection.get_presets())
|
||||||
|
if (preset.name == selection.name && is_target(preset))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
if (dirty_target_was_removed(print_selection, staged.prints) ||
|
||||||
|
dirty_target_was_removed(filament_selection, staged.filaments) ||
|
||||||
|
dirty_target_was_removed(printer_selection, staged.printers))
|
||||||
|
return fail("A modified selected preset was removed or renamed");
|
||||||
|
|
||||||
|
auto external_child_name = [&](const PresetCollection& collection) {
|
||||||
|
auto depends_on_target = [&](const Preset& preset) {
|
||||||
|
if (is_target(preset))
|
||||||
|
return false;
|
||||||
|
const Preset* parent = &preset;
|
||||||
|
std::unordered_set<const Preset*> visited;
|
||||||
|
while ((parent = collection.get_preset_parent(*parent)) != nullptr && visited.insert(parent).second)
|
||||||
|
if (is_target(*parent))
|
||||||
|
return true;
|
||||||
|
for (const char* key : {"compatible_prints", "compatible_printers"}) {
|
||||||
|
const auto* names = preset.config.option<ConfigOptionStrings>(key);
|
||||||
|
if (names == nullptr)
|
||||||
|
continue;
|
||||||
|
const PresetCollection& referenced = std::string(key) == "compatible_printers" ? printers : prints;
|
||||||
|
for (const std::string& name : names->values) {
|
||||||
|
const Preset* dependency = referenced.find_preset(name, false);
|
||||||
|
if (dependency != nullptr && is_target(*dependency))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
for (const Preset& preset : collection.get_presets())
|
||||||
|
if (depends_on_target(preset))
|
||||||
|
return preset.name;
|
||||||
|
if (depends_on_target(collection.get_edited_preset()))
|
||||||
|
return collection.get_edited_preset().name;
|
||||||
|
return std::string();
|
||||||
|
};
|
||||||
|
for (const PresetCollection* collection : std::array<const PresetCollection*, 3>{&prints, &filaments, &printers}) {
|
||||||
|
const std::string child_name = external_child_name(*collection);
|
||||||
|
if (!child_name.empty())
|
||||||
|
return fail("Preset outside this bundle depends on it: " + child_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto restore_selection = [](PresetCollection& collection, const Selection& selection) {
|
||||||
|
if (collection.find_preset(selection.name, false, true) == nullptr) {
|
||||||
|
collection.select_preset(collection.first_visible_idx());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
collection.select_preset_by_name(selection.name, true);
|
||||||
|
if (collection.get_selected_preset_name() != selection.name)
|
||||||
|
return false;
|
||||||
|
Preset& edited = collection.get_edited_preset();
|
||||||
|
for (const std::string& key : selection.dirty_options) {
|
||||||
|
if (const ConfigOption* option = selection.edited.config.option(key))
|
||||||
|
edited.config.set_key_value(key, option->clone());
|
||||||
|
else
|
||||||
|
edited.config.erase(key);
|
||||||
|
}
|
||||||
|
collection.update_dirty();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
staged.prints.update_map_alias_to_profile_name();
|
||||||
|
staged.filaments.update_map_alias_to_profile_name();
|
||||||
|
staged.printers.update_map_alias_to_profile_name();
|
||||||
|
const bool printer_preserved = restore_selection(staged.printers, printer_selection);
|
||||||
|
const bool print_preserved = restore_selection(staged.prints, print_selection);
|
||||||
|
const bool filament_preserved = restore_selection(staged.filaments, filament_selection);
|
||||||
|
const auto selection_mode = [printer_preserved](bool selection_preserved) {
|
||||||
|
if (!selection_preserved)
|
||||||
|
return PresetSelectCompatibleType::Always;
|
||||||
|
return printer_preserved ? PresetSelectCompatibleType::Never : PresetSelectCompatibleType::OnlyIfWasCompatible;
|
||||||
|
};
|
||||||
|
if (staged.printers.get_edited_preset().printer_technology() != ptFFF)
|
||||||
|
return fail("Local bundle reload requires an FFF printer selection");
|
||||||
|
staged.update_compatible(selection_mode(print_preserved), selection_mode(filament_preserved));
|
||||||
|
for (std::string& name : staged.filament_presets)
|
||||||
|
if (boost::starts_with(name, target_prefix) && staged.filaments.find_preset(name, false) == nullptr)
|
||||||
|
name = staged.filaments.first_compatible().name;
|
||||||
|
staged.update_multi_material_filament_presets();
|
||||||
|
|
||||||
|
struct Collection
|
||||||
|
{
|
||||||
|
PresetCollection& live;
|
||||||
|
PresetCollection& candidate;
|
||||||
|
size_t original_size;
|
||||||
|
};
|
||||||
|
std::array<Collection, 3> collections{{{prints, staged.prints, prints.size()},
|
||||||
|
{filaments, staged.filaments, filaments.size()},
|
||||||
|
{printers, staged.printers, printers.size()}}};
|
||||||
|
for (size_t i = 0; i < collections.size(); ++i) {
|
||||||
|
const auto& collection = collections[i];
|
||||||
|
const auto dirty_options = collection.live.current_dirty_options();
|
||||||
|
if (dirty_options.empty())
|
||||||
|
continue;
|
||||||
|
if (collection.live.get_selected_preset_name() != collection.candidate.get_selected_preset_name())
|
||||||
|
return fail("Reload would discard a modified selected preset");
|
||||||
|
for (const std::string& key : dirty_options) {
|
||||||
|
const ConfigOption* before = collection.live.get_edited_preset().config.option(key);
|
||||||
|
const ConfigOption* after = collection.candidate.get_edited_preset().config.option(key);
|
||||||
|
if ((before == nullptr) != (after == nullptr) || (before != nullptr && *before != *after))
|
||||||
|
return fail("Reload would discard a modified preset option: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (calibrate_printer != nullptr && staged.printers.find_preset(calibrate_printer->name, false, true) != nullptr)
|
||||||
|
staged.set_calibrate_printer(calibrate_printer->name);
|
||||||
|
std::array<std::pair<ConfigOptionFloats*, ConfigOptionFloats*>, 3> flush_options;
|
||||||
|
size_t i = 0;
|
||||||
|
for (const char* key : {"flush_volumes_matrix", "flush_volumes_vector", "flush_multiplier"})
|
||||||
|
flush_options[i++] = {project_config.option<ConfigOptionFloats>(key), staged.project_config.option<ConfigOptionFloats>(key)};
|
||||||
|
|
||||||
|
metadata.bundle_type = BundleType::Local;
|
||||||
|
metadata.is_subscribed = false;
|
||||||
|
metadata.update_available = false;
|
||||||
|
metadata.path = metadata_file.string();
|
||||||
|
fs::path local_dir = user_dir / PRESET_LOCAL_DIR;
|
||||||
|
std::unique_lock<std::shared_mutex> lock(bundles.RWMtx);
|
||||||
|
const auto existing = bundles.m_bundles.find(bundle_id);
|
||||||
|
if (existing != bundles.m_bundles.end() &&
|
||||||
|
(existing->second.bundle_type == BundleType::Subscribed || existing->second.is_subscribed))
|
||||||
|
return fail("A subscribed bundle already uses this id");
|
||||||
|
|
||||||
|
std::set<const Preset*> calibration_filaments;
|
||||||
|
const Preset* calibration_printer = nullptr;
|
||||||
|
try {
|
||||||
|
// Growing a deque preserves existing objects. Roll back appended copies if preparation fails.
|
||||||
|
for (size_t i = 0; i < collections.size(); ++i) {
|
||||||
|
auto& collection = collections[i];
|
||||||
|
while (collection.live.size() < collection.candidate.size())
|
||||||
|
collection.live.m_presets.emplace_back(collection.candidate.m_presets[collection.live.size()]);
|
||||||
|
}
|
||||||
|
auto calibration_preset = [](const Preset* preset, PresetCollection& live, PresetCollection& candidate) -> const Preset* {
|
||||||
|
if (preset == nullptr)
|
||||||
|
return nullptr;
|
||||||
|
if (preset == &candidate.get_edited_preset())
|
||||||
|
return &live.get_edited_preset();
|
||||||
|
const auto it = candidate.find_preset_internal(preset->name);
|
||||||
|
return &live.m_presets[it - candidate.m_presets.begin()];
|
||||||
|
};
|
||||||
|
calibration_printer = calibration_preset(staged.calibrate_printer, printers, staged.printers);
|
||||||
|
for (const Preset* preset : staged.calibrate_filaments)
|
||||||
|
calibration_filaments.insert(calibration_preset(preset, filaments, staged.filaments));
|
||||||
|
// Insertion is the last allocating operation. Replacing metadata only moves its members.
|
||||||
|
bundles.m_bundles.insert_or_assign(bundle_id, std::move(metadata));
|
||||||
|
} catch (...) {
|
||||||
|
for (size_t i = 0; i < collections.size(); ++i)
|
||||||
|
while (collections[i].live.size() > collections[i].original_size)
|
||||||
|
collections[i].live.m_presets.pop_back();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
auto install_target = [](PresetCollection& live, PresetCollection& candidate) noexcept {
|
||||||
|
// Keep stored Preset references alive when the collection's layout is unchanged.
|
||||||
|
// Adding/removing presets retains the collection's usual sorted-position semantics.
|
||||||
|
for (size_t i = 0; i < candidate.size(); ++i)
|
||||||
|
live.m_presets[i] = std::move(candidate.m_presets[i]);
|
||||||
|
while (live.size() > candidate.size())
|
||||||
|
live.m_presets.pop_back();
|
||||||
|
live.m_map_alias_to_profile_name.swap(candidate.m_map_alias_to_profile_name);
|
||||||
|
// Move assignment does not allocate (including Semver); swapping Presets would construct temporary containers.
|
||||||
|
live.m_edited_preset = std::move(candidate.m_edited_preset);
|
||||||
|
live.m_saved_preset = std::move(candidate.m_saved_preset);
|
||||||
|
live.m_idx_selected = candidate.m_idx_selected;
|
||||||
|
};
|
||||||
|
for (size_t i = 0; i < collections.size(); ++i)
|
||||||
|
install_target(collections[i].live, collections[i].candidate);
|
||||||
|
filament_presets.swap(staged.filament_presets);
|
||||||
|
for (const auto& options : flush_options)
|
||||||
|
options.first->values.swap(options.second->values);
|
||||||
|
calibrate_printer = calibration_printer;
|
||||||
|
calibrate_filaments.swap(calibration_filaments);
|
||||||
|
dir_user_presets_local.swap(local_dir);
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& exception) {
|
||||||
|
return fail(std::string("Failed to reload local bundle: ") + exception.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PresetsConfigSubstitutions PresetBundle::load_user_presets(AppConfig & config,
|
PresetsConfigSubstitutions PresetBundle::load_user_presets(AppConfig & config,
|
||||||
std::map<std::string, std::map<std::string, std::string>> &my_presets,
|
std::map<std::string, std::map<std::string, std::string>> &my_presets,
|
||||||
ForwardCompatibilitySubstitutionRule substitution_rule)
|
ForwardCompatibilitySubstitutionRule substitution_rule)
|
||||||
@@ -3898,6 +4230,8 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
|
|||||||
// BBS: change directories by design
|
// BBS: change directories by design
|
||||||
void PresetBundle::export_selections(AppConfig &config)
|
void PresetBundle::export_selections(AppConfig &config)
|
||||||
{
|
{
|
||||||
|
if (m_selection_export_blockers > 0)
|
||||||
|
return;
|
||||||
assert(this->printers.get_edited_preset().printer_technology() != ptFFF || filament_presets.size() >= 1);
|
assert(this->printers.get_edited_preset().printer_technology() != ptFFF || filament_presets.size() >= 1);
|
||||||
//assert(this->printers.get_edited_preset().printer_technology() != ptFFF || filament_presets.size() > 1 || filaments.get_selected_preset_name() == filament_presets.front());
|
//assert(this->printers.get_edited_preset().printer_technology() != ptFFF || filament_presets.size() > 1 || filaments.get_selected_preset_name() == filament_presets.front());
|
||||||
config.clear_section("presets");
|
config.clear_section("presets");
|
||||||
@@ -5524,12 +5858,10 @@ DynamicPrintConfig PresetBundle::full_sla_config() const
|
|||||||
ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||||
{
|
{
|
||||||
if (is_gcode_file(path)) {
|
if (is_gcode_file(path)) {
|
||||||
DynamicPrintConfig config;
|
|
||||||
//BBS: add config related logs
|
//BBS: add config related logs
|
||||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, gcodefile %1%, compatibility_rule %2%")%path %compatibility_rule;
|
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, gcodefile %1%, compatibility_rule %2%")%path %compatibility_rule;
|
||||||
config.apply(FullPrintConfig::defaults());
|
ConfigSubstitutions config_substitutions;
|
||||||
ConfigSubstitutions config_substitutions = config.load_from_gcode_file(path, compatibility_rule);
|
DynamicPrintConfig config = config_from_gcode(path, compatibility_rule, config_substitutions);
|
||||||
Preset::normalize(config);
|
|
||||||
load_config_file_config(path, true, std::move(config));
|
load_config_file_config(path, true, std::move(config));
|
||||||
return config_substitutions;
|
return config_substitutions;
|
||||||
}
|
}
|
||||||
@@ -5541,6 +5873,79 @@ ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, Forw
|
|||||||
return ConfigSubstitutions{};
|
return ConfigSubstitutions{};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DynamicPrintConfig PresetBundle::config_from_gcode(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule,
|
||||||
|
ConfigSubstitutions &substitutions) const
|
||||||
|
{
|
||||||
|
DynamicPrintConfig config;
|
||||||
|
config.apply(FullPrintConfig::defaults());
|
||||||
|
// The config block leaves a few project settings out (filament_colour_type, prime_volume_mode,
|
||||||
|
// flush_multiplier_fast); those keep this bundle's values rather than falling back to the defaults.
|
||||||
|
config.apply(this->project_config);
|
||||||
|
const DynamicPrintConfig base = config;
|
||||||
|
substitutions = config.load_from_gcode_file(path, compatibility_rule);
|
||||||
|
// An empty list of choices sets nothing: G-code written by a tool rather than from presets leaves per-extruder
|
||||||
|
// lists such as nozzle_volume_type empty, and the settings tabs expect a choice per extruder. An empty text
|
||||||
|
// list, such as filament_multi_colour, is a value of its own and stays.
|
||||||
|
for (const std::string &key : config.keys())
|
||||||
|
if (const ConfigOption *opt = config.option(key); opt->type() == coEnums && static_cast<const ConfigOptionVectorBase *>(opt)->size() == 0)
|
||||||
|
if (const ConfigOption *below = base.option(key); below != nullptr && static_cast<const ConfigOptionVectorBase *>(below)->size() > 0)
|
||||||
|
config.set_key_value(key, below->clone());
|
||||||
|
Preset::normalize(config);
|
||||||
|
// The block holds flush_volumes_matrix already multiplied by the multiplier the slice used, so a
|
||||||
|
// multiplier of 1 keeps the purge volumes the file was sliced with. G-code from before 2.3.2 held the
|
||||||
|
// plain matrix and loses a multiplier other than 1.
|
||||||
|
for (const char *key : { "flush_multiplier", "flush_multiplier_fast" })
|
||||||
|
if (auto *multiplier = config.option<ConfigOptionFloats>(key))
|
||||||
|
std::fill(multiplier->values.begin(), multiplier->values.end(), 1.);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<PresetBundle::Selection> PresetBundle::selection() const
|
||||||
|
{
|
||||||
|
auto capture = [](const PresetCollection &presets, Selection::Collection &out) {
|
||||||
|
if (std::any_of(presets.m_presets.begin(), presets.m_presets.end(), [](const Preset &preset) { return preset.is_project_embedded; }))
|
||||||
|
return false;
|
||||||
|
out.selected = presets.get_selected_preset_name();
|
||||||
|
out.edited = presets.get_edited_preset().config;
|
||||||
|
out.saved = presets.m_saved_preset.config;
|
||||||
|
out.visible.reserve(presets.m_presets.size());
|
||||||
|
for (const Preset &preset : presets.m_presets)
|
||||||
|
out.visible.push_back(preset.is_visible);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
Selection selection;
|
||||||
|
if (!capture(this->prints, selection.prints) || !capture(this->filaments, selection.filaments) || !capture(this->printers, selection.printers))
|
||||||
|
return std::nullopt;
|
||||||
|
selection.filament_presets = this->filament_presets;
|
||||||
|
selection.project_config = this->project_config;
|
||||||
|
selection.extruder_ams_counts = this->extruder_ams_counts;
|
||||||
|
return selection;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PresetBundle::restore_selection(const Selection &selection)
|
||||||
|
{
|
||||||
|
auto restore = [](PresetCollection &presets, const Selection::Collection &saved) {
|
||||||
|
presets.reset_project_embedded_presets();
|
||||||
|
// Before selecting: select_preset_by_name() passes over a hidden preset.
|
||||||
|
assert(presets.m_presets.size() == saved.visible.size());
|
||||||
|
for (size_t i = 0; i < std::min(presets.m_presets.size(), saved.visible.size()); ++i)
|
||||||
|
presets.m_presets[i].is_visible = saved.visible[i];
|
||||||
|
presets.select_preset_by_name(saved.selected, true);
|
||||||
|
presets.m_saved_preset.config = saved.saved;
|
||||||
|
presets.get_edited_preset().config = saved.edited;
|
||||||
|
presets.update_dirty();
|
||||||
|
};
|
||||||
|
restore(this->printers, selection.printers);
|
||||||
|
restore(this->prints, selection.prints);
|
||||||
|
restore(this->filaments, selection.filaments);
|
||||||
|
this->update_compatible(PresetSelectCompatibleType::Never);
|
||||||
|
this->update_multi_material_filament_presets();
|
||||||
|
// Last, as update_multi_material_filament_presets() resizes filament_presets and the flush data in project_config.
|
||||||
|
this->filament_presets = selection.filament_presets;
|
||||||
|
this->project_config = selection.project_config;
|
||||||
|
this->extruder_ams_counts = selection.extruder_ams_counts;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//some filament presets split from one to sperate ones
|
//some filament presets split from one to sperate ones
|
||||||
//following map recording these filament presets
|
//following map recording these filament presets
|
||||||
|
|||||||
@@ -302,6 +302,9 @@ public:
|
|||||||
// BBS Load user presets
|
// BBS Load user presets
|
||||||
PresetsConfigSubstitutions load_user_presets(std::string user, ForwardCompatibilitySubstitutionRule rule, bool read_only = false);
|
PresetsConfigSubstitutions load_user_presets(std::string user, ForwardCompatibilitySubstitutionRule rule, bool read_only = false);
|
||||||
PresetsConfigSubstitutions load_user_presets(AppConfig &config, std::map<std::string, std::map<std::string, std::string>>& my_presets, ForwardCompatibilitySubstitutionRule rule);
|
PresetsConfigSubstitutions load_user_presets(AppConfig &config, std::map<std::string, std::map<std::string, std::string>>& my_presets, ForwardCompatibilitySubstitutionRule rule);
|
||||||
|
|
||||||
|
bool reload_local_bundle(const std::string& preset_folder, const std::string& bundle_id, std::string* error = nullptr);
|
||||||
|
std::vector<std::string> list_local_bundle_ids(const std::string& preset_folder);
|
||||||
// Orca: Import subscribed bundle presets (load and save to disk in one operation), handles one bundle at a time
|
// Orca: Import subscribed bundle presets (load and save to disk in one operation), handles one bundle at a time
|
||||||
PresetsConfigSubstitutions update_subscribed_presets(AppConfig& config,
|
PresetsConfigSubstitutions update_subscribed_presets(AppConfig& config,
|
||||||
const std::map<std::string, std::map<std::string, std::string>>& bundle_presets,
|
const std::map<std::string, std::map<std::string, std::string>>& bundle_presets,
|
||||||
@@ -397,6 +400,28 @@ public:
|
|||||||
std::vector<Preset*> get_current_project_embedded_presets();
|
std::vector<Preset*> get_current_project_embedded_presets();
|
||||||
void reset_project_embedded_presets();
|
void reset_project_embedded_presets();
|
||||||
|
|
||||||
|
// What importing a file's settings replaces, so that an import which fails part-way, or whose
|
||||||
|
// file is then rejected, can be undone without copying every preset.
|
||||||
|
struct Selection
|
||||||
|
{
|
||||||
|
struct Collection
|
||||||
|
{
|
||||||
|
std::string selected;
|
||||||
|
DynamicPrintConfig edited;
|
||||||
|
// The baseline saved_is_dirty() compares against, which feeds the project dirty state.
|
||||||
|
DynamicPrintConfig saved;
|
||||||
|
// By index: an import only inserts project-embedded presets, which a restore drops first.
|
||||||
|
std::vector<bool> visible;
|
||||||
|
};
|
||||||
|
Collection prints, filaments, printers;
|
||||||
|
std::vector<std::string> filament_presets;
|
||||||
|
DynamicPrintConfig project_config;
|
||||||
|
std::vector<std::map<int, int>> extruder_ams_counts;
|
||||||
|
};
|
||||||
|
// Nothing while project-embedded presets exist: a restore drops them, which would lose a loaded project's presets.
|
||||||
|
std::optional<Selection> selection() const;
|
||||||
|
void restore_selection(const Selection &selection);
|
||||||
|
|
||||||
//BBS: find printer model
|
//BBS: find printer model
|
||||||
std::string get_texture_for_printer_model(std::string model_name);
|
std::string get_texture_for_printer_model(std::string model_name);
|
||||||
std::string get_stl_model_for_printer_model(std::string model_name);
|
std::string get_stl_model_for_printer_model(std::string model_name);
|
||||||
@@ -405,6 +430,21 @@ public:
|
|||||||
// Export selections (current print, current filaments, current printer) into config.ini
|
// Export selections (current print, current filaments, current printer) into config.ini
|
||||||
void export_selections(AppConfig &config);
|
void export_selections(AppConfig &config);
|
||||||
|
|
||||||
|
// While one lives, export_selections() writes nothing, so a selection only shown for a while (the
|
||||||
|
// settings of a G-code being previewed) is not remembered as the user's. It also covers the
|
||||||
|
// exports the GUI makes on its own while refreshing.
|
||||||
|
class SelectionExportBlocker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit SelectionExportBlocker(PresetBundle &bundle) : m_bundle(bundle) { ++m_bundle.m_selection_export_blockers; }
|
||||||
|
~SelectionExportBlocker() { --m_bundle.m_selection_export_blockers; }
|
||||||
|
SelectionExportBlocker(const SelectionExportBlocker &) = delete;
|
||||||
|
SelectionExportBlocker &operator=(const SelectionExportBlocker &) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
PresetBundle &m_bundle;
|
||||||
|
};
|
||||||
|
|
||||||
// n is the total slot count, and growth appends at the raw tail - which is where the mixed
|
// n is the total slot count, and growth appends at the raw tail - which is where the mixed
|
||||||
// slots live. A caller adding physical filaments has to add num_mixed_filaments() on top and
|
// slots live. A caller adding physical filaments has to add num_mixed_filaments() on top and
|
||||||
// then move the new slots ahead of the mixed tail, as Sidebar::add_custom_filament does.
|
// then move the new slots ahead of the mixed tail, as Sidebar::add_custom_filament does.
|
||||||
@@ -546,6 +586,11 @@ public:
|
|||||||
// If the file is loaded successfully, its print / filament / printer profiles will be activated.
|
// If the file is loaded successfully, its print / filament / printer profiles will be activated.
|
||||||
ConfigSubstitutions load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
ConfigSubstitutions load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||||
|
|
||||||
|
// The settings an OrcaSlicer G-code was sliced with, from its config block, ready for load_config_model().
|
||||||
|
// Throws when the file carries no such block (another slicer, or the block was left out).
|
||||||
|
DynamicPrintConfig config_from_gcode(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule,
|
||||||
|
ConfigSubstitutions &substitutions) const;
|
||||||
|
|
||||||
// Load a config bundle file, into presets and store the loaded presets into separate files
|
// Load a config bundle file, into presets and store the loaded presets into separate files
|
||||||
// of the local configuration directory.
|
// of the local configuration directory.
|
||||||
// Load settings into the provided settings instance.
|
// Load settings into the provided settings instance.
|
||||||
@@ -896,6 +941,9 @@ private:
|
|||||||
std::string vendor_to_validate = "";
|
std::string vendor_to_validate = "";
|
||||||
int m_errors = 0;
|
int m_errors = 0;
|
||||||
|
|
||||||
|
// The live SelectionExportBlockers.
|
||||||
|
int m_selection_export_blockers = 0;
|
||||||
|
|
||||||
// Helper function: save preset to bundle directory with common logic
|
// Helper function: save preset to bundle directory with common logic
|
||||||
bool save_preset_to_bundle_dir(Preset& preset, PresetCollection* collection,
|
bool save_preset_to_bundle_dir(Preset& preset, PresetCollection* collection,
|
||||||
const std::string& bundle_id, const std::string& type_subdir,
|
const std::string& bundle_id, const std::string& type_subdir,
|
||||||
|
|||||||
@@ -2235,9 +2235,11 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
|
|||||||
//FIXME It is quite expensive to generate object layers just to get the print height!
|
//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);
|
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()) {
|
if (!layers.empty()) {
|
||||||
effective_max_z = layers.back();
|
// 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();
|
||||||
last_layer_below_max = layers.size() >= 2 &&
|
last_layer_below_max = layers.size() >= 2 &&
|
||||||
0.5 * (layers[layers.size() - 2] + layers.back()) <= this->config().printable_height + EPSILON;
|
raft_z + 0.5 * (layers[layers.size() - 2] + layers.back()) <= this->config().printable_height + EPSILON;
|
||||||
have_height = true;
|
have_height = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2245,7 +2247,11 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
|
|||||||
if (!have_height)
|
if (!have_height)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (shrinkage_compensation_z != 1. && effective_max_z > (this->config().printable_height / shrinkage_compensation_z + EPSILON)) {
|
// 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) {
|
||||||
// The object exceeds the maximum build volume height because of shrinkage compensation.
|
// The object exceeds the maximum build volume height because of shrinkage compensation.
|
||||||
return StringObjectException{
|
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),
|
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),
|
||||||
|
|||||||
@@ -174,6 +174,8 @@ set(SLIC3R_GUI_SOURCES
|
|||||||
GUI/Gizmos/GLGizmoAssembly.hpp
|
GUI/Gizmos/GLGizmoAssembly.hpp
|
||||||
GUI/Gizmos/GLGizmoBase.cpp
|
GUI/Gizmos/GLGizmoBase.cpp
|
||||||
GUI/Gizmos/GLGizmoBase.hpp
|
GUI/Gizmos/GLGizmoBase.hpp
|
||||||
|
GUI/Gizmos/GLGizmoAlignment.cpp
|
||||||
|
GUI/Gizmos/GLGizmoAlignment.hpp
|
||||||
GUI/Gizmos/GLGizmoBrimEars.cpp
|
GUI/Gizmos/GLGizmoBrimEars.cpp
|
||||||
GUI/Gizmos/GLGizmoBrimEars.hpp
|
GUI/Gizmos/GLGizmoBrimEars.hpp
|
||||||
GUI/Gizmos/GLGizmoCut.cpp
|
GUI/Gizmos/GLGizmoCut.cpp
|
||||||
@@ -683,6 +685,8 @@ set(SLIC3R_GUI_SOURCES
|
|||||||
plugin/host/PluginHostUi.hpp
|
plugin/host/PluginHostUi.hpp
|
||||||
plugin/host/PluginPages.cpp
|
plugin/host/PluginPages.cpp
|
||||||
plugin/host/PluginPages.hpp
|
plugin/host/PluginPages.hpp
|
||||||
|
plugin/host/PluginResourceTextures.cpp
|
||||||
|
plugin/host/PluginResourceTextures.hpp
|
||||||
plugin/CloudPluginService.cpp
|
plugin/CloudPluginService.cpp
|
||||||
plugin/CloudPluginService.hpp
|
plugin/CloudPluginService.hpp
|
||||||
plugin/PluginFsUtils.cpp
|
plugin/PluginFsUtils.cpp
|
||||||
@@ -706,6 +710,9 @@ set(SLIC3R_GUI_SOURCES
|
|||||||
plugin/pluginTypes/pages/PagesPluginCapability.hpp
|
plugin/pluginTypes/pages/PagesPluginCapability.hpp
|
||||||
plugin/pluginTypes/pages/PagesPluginCapability.cpp
|
plugin/pluginTypes/pages/PagesPluginCapability.cpp
|
||||||
plugin/pluginTypes/pages/PagesPluginCapabilityTrampoline.hpp
|
plugin/pluginTypes/pages/PagesPluginCapabilityTrampoline.hpp
|
||||||
|
plugin/pluginTypes/resourceTexture/ResourceTexturePluginCapability.hpp
|
||||||
|
plugin/pluginTypes/resourceTexture/ResourceTexturePluginCapability.cpp
|
||||||
|
plugin/pluginTypes/resourceTexture/ResourceTexturePluginCapabilityTrampoline.hpp
|
||||||
plugin/pluginTypes/script/ScriptPluginCapability.hpp
|
plugin/pluginTypes/script/ScriptPluginCapability.hpp
|
||||||
plugin/pluginTypes/script/ScriptPluginCapability.cpp
|
plugin/pluginTypes/script/ScriptPluginCapability.cpp
|
||||||
plugin/pluginTypes/script/ScriptPluginCapabilityTrampoline.hpp
|
plugin/pluginTypes/script/ScriptPluginCapabilityTrampoline.hpp
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "ConnectPrinter.hpp"
|
#include "ConnectPrinter.hpp"
|
||||||
#include "GUI_App.hpp"
|
#include "GUI_App.hpp"
|
||||||
|
#include "slic3r/Utils/NetworkAgentFactory.hpp"
|
||||||
#include "slic3r/GUI/GUI_Utils.hpp"
|
#include "slic3r/GUI/GUI_Utils.hpp"
|
||||||
#include "slic3r/GUI/GUI.hpp"
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
#include "slic3r/GUI/DeviceCore/DevConfigUtil.h"
|
#include "slic3r/GUI/DeviceCore/DevConfigUtil.h"
|
||||||
@@ -169,7 +170,7 @@ void ConnectPrinterDialog::on_input_enter(wxCommandEvent& evt)
|
|||||||
void ConnectPrinterDialog::on_button_confirm(wxCommandEvent &event)
|
void ConnectPrinterDialog::on_button_confirm(wxCommandEvent &event)
|
||||||
{
|
{
|
||||||
wxString code = m_textCtrl_code->GetTextCtrl()->GetValue();
|
wxString code = m_textCtrl_code->GetTextCtrl()->GetValue();
|
||||||
if (code.empty())
|
if (code.empty() && (!m_obj || m_obj->printer_agent_id != MOONRAKER_PRINTER_AGENT_ID))
|
||||||
code = "88888888";
|
code = "88888888";
|
||||||
for (char c : code) {
|
for (char c : code) {
|
||||||
if (!(('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'))) {
|
if (!(('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'))) {
|
||||||
|
|||||||
@@ -276,6 +276,18 @@ namespace Slic3r
|
|||||||
std::string connect_type = j["connect_type"].get<std::string>();
|
std::string connect_type = j["connect_type"].get<std::string>();
|
||||||
std::string bind_state = j["bind_state"].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") {
|
if (connect_type == "farm") {
|
||||||
connect_type ="lan";
|
connect_type ="lan";
|
||||||
bind_state = "free";
|
bind_state = "free";
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "HMS.hpp"
|
#include "HMS.hpp"
|
||||||
#include "I18N.hpp"
|
#include "I18N.hpp"
|
||||||
#include "libslic3r/Thread.hpp"
|
#include "libslic3r/Thread.hpp"
|
||||||
|
#include "libslic3r/PresetBundle.hpp"
|
||||||
#include "slic3r/Utils/Http.hpp"
|
#include "slic3r/Utils/Http.hpp"
|
||||||
#include "slic3r/Utils/NetworkAgent.hpp"
|
#include "slic3r/Utils/NetworkAgent.hpp"
|
||||||
#include "slic3r/Utils/NetworkAgentFactory.hpp"
|
#include "slic3r/Utils/NetworkAgentFactory.hpp"
|
||||||
@@ -66,6 +67,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <wx/colour.h>
|
#include <wx/colour.h>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
#include <wx/app.h>
|
||||||
#include <wx/dir.h>
|
#include <wx/dir.h>
|
||||||
#include <wx/string.h>
|
#include <wx/string.h>
|
||||||
#include <wx/event.h>
|
#include <wx/event.h>
|
||||||
@@ -527,6 +529,18 @@ bool MachineObject::HasRecentLanMessage()
|
|||||||
return diff.count() < 5000;
|
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
|
std::string MachineObject::get_access_code() const
|
||||||
{
|
{
|
||||||
return access_code;
|
return access_code;
|
||||||
@@ -535,6 +549,7 @@ std::string MachineObject::get_access_code() const
|
|||||||
void MachineObject::set_access_code(std::string code, bool only_refresh)
|
void MachineObject::set_access_code(std::string code, bool only_refresh)
|
||||||
{
|
{
|
||||||
this->access_code = code;
|
this->access_code = code;
|
||||||
|
m_access_revoked = false;
|
||||||
if (only_refresh) {
|
if (only_refresh) {
|
||||||
AppConfig* config = m_manager ? m_manager->get_app_config() : GUI::wxGetApp().app_config;
|
AppConfig* config = m_manager ? m_manager->get_app_config() : GUI::wxGetApp().app_config;
|
||||||
if (config) {
|
if (config) {
|
||||||
@@ -548,25 +563,10 @@ void MachineObject::set_access_code(std::string code, bool only_refresh)
|
|||||||
// session boundary, since parse_user_print_info() always overwrites their code
|
// 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
|
// fresh from the cloud API's current response, so there's no cross-agent leakage
|
||||||
// risk to guard against there.
|
// risk to guard against there.
|
||||||
if (!code.empty()) {
|
if (has_access_right()) {
|
||||||
DeviceManager::update_local_machine(*this, config);
|
DeviceManager::update_local_machine(*this, config);
|
||||||
} else {
|
} else {
|
||||||
// Only patch an existing record's code - don't persist a brand-new
|
config->clear_local_machine_access_code(get_dev_id(), printer_agent_id);
|
||||||
// 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 {
|
} else {
|
||||||
if (!code.empty())
|
if (!code.empty())
|
||||||
@@ -2678,7 +2678,7 @@ void MachineObject::update_print_progress(const json& value)
|
|||||||
|
|
||||||
int MachineObject::connect()
|
int MachineObject::connect()
|
||||||
{
|
{
|
||||||
if (get_dev_ip().empty()) return -1;
|
if (m_access_revoked || get_dev_ip().empty()) return -1;
|
||||||
std::string username = m_agent ? m_agent->default_lan_username() : std::string();
|
std::string username = m_agent ? m_agent->default_lan_username() : std::string();
|
||||||
std::string password = get_access_code();
|
std::string password = get_access_code();
|
||||||
|
|
||||||
@@ -2691,17 +2691,25 @@ int MachineObject::connect()
|
|||||||
std::string host = Http::get_host_from_url(input, &port);
|
std::string host = Http::get_host_from_url(input, &port);
|
||||||
std::string ca_file;
|
std::string ca_file;
|
||||||
|
|
||||||
if (GUI::wxGetApp().preset_bundle) {
|
if (wxTheApp && GUI::wxGetApp().preset_bundle) {
|
||||||
const auto& config = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
const auto& config = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||||
if (port.empty())
|
if (port.empty())
|
||||||
port = config.opt_string("printhost_port");
|
port = config.opt_string("printhost_port");
|
||||||
ca_file = config.opt_string("printhost_cafile");
|
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())
|
if (host.empty())
|
||||||
host = get_dev_ip();
|
host = get_dev_ip();
|
||||||
|
|
||||||
|
|
||||||
if (m_agent) {
|
if (m_agent) {
|
||||||
try {
|
try {
|
||||||
PrinterConnectionParams params{
|
PrinterConnectionParams params{
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ private:
|
|||||||
std::string dev_name;
|
std::string dev_name;
|
||||||
std::string dev_ip;
|
std::string dev_ip;
|
||||||
std::string access_code;
|
std::string access_code;
|
||||||
|
bool m_access_revoked = false;
|
||||||
|
|
||||||
// type, time stamp, delay
|
// type, time stamp, delay
|
||||||
std::vector<std::tuple<std::string, uint64_t, uint64_t>> message_delay;
|
std::vector<std::tuple<std::string, uint64_t, uint64_t>> message_delay;
|
||||||
@@ -229,7 +230,8 @@ public:
|
|||||||
std::string dev_connection_name; /* lan | eth */
|
std::string dev_connection_name; /* lan | eth */
|
||||||
|
|
||||||
/*access code*/
|
/*access code*/
|
||||||
bool has_access_right() const { return !get_access_code().empty(); }
|
bool has_access_right() const;
|
||||||
|
void revoke_access();
|
||||||
std::string get_access_code() const;
|
std::string get_access_code() const;
|
||||||
void set_access_code(std::string code, bool only_refresh = true);
|
void set_access_code(std::string code, bool only_refresh = true);
|
||||||
|
|
||||||
|
|||||||
@@ -1797,7 +1797,9 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
|
|||||||
}
|
}
|
||||||
else {*/
|
else {*/
|
||||||
//BBS: add only gcode mode
|
//BBS: add only gcode mode
|
||||||
if (m_only_gcode_in_preview) {
|
// When the file's own settings were loaded into the presets, the bed already is the file's, with the
|
||||||
|
// model and texture of a user or custom printer too, which the lookup below cannot resolve.
|
||||||
|
if (m_only_gcode_in_preview && !wxGetApp().plater()->gcode_settings_loaded()) {
|
||||||
Pointfs printable_area;
|
Pointfs printable_area;
|
||||||
//BBS: add bed exclude area
|
//BBS: add bed exclude area
|
||||||
Pointfs bed_exclude_area = Pointfs();
|
Pointfs bed_exclude_area = Pointfs();
|
||||||
|
|||||||
@@ -5520,7 +5520,7 @@ void GLCanvas3D::set_tooltip(const std::string& tooltip)
|
|||||||
m_tooltip.set_text(tooltip);
|
m_tooltip.set_text(tooltip);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLCanvas3D::do_move(const std::string& snapshot_type)
|
void GLCanvas3D::do_move(const std::string& snapshot_type, bool force_volume_move)
|
||||||
{
|
{
|
||||||
if (m_model == nullptr)
|
if (m_model == nullptr)
|
||||||
return;
|
return;
|
||||||
@@ -5555,7 +5555,15 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
|
|||||||
if (model_object == nullptr)
|
if (model_object == nullptr)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (selection_mode == Selection::Instance) {
|
if (force_volume_move || selection_mode == Selection::Volume) {
|
||||||
|
auto cur_mv = model_object->volumes[volume_idx];
|
||||||
|
if (cur_mv->get_transformation() != v->get_volume_transformation()) {
|
||||||
|
cur_mv->set_transformation(v->get_volume_transformation());
|
||||||
|
// BBS: backup
|
||||||
|
Slic3r::save_object_mesh(*model_object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (selection_mode == Selection::Instance) {
|
||||||
if (m_canvas_type == GLCanvas3D::ECanvasType::CanvasAssembleView) {
|
if (m_canvas_type == GLCanvas3D::ECanvasType::CanvasAssembleView) {
|
||||||
if ((model_object->instances[instance_idx]->get_assemble_offset() - v->get_instance_offset()).norm() > 1e-2) {
|
if ((model_object->instances[instance_idx]->get_assemble_offset() - v->get_instance_offset()).norm() > 1e-2) {
|
||||||
model_object->instances[instance_idx]->set_assemble_transformation(v->get_instance_transformation());
|
model_object->instances[instance_idx]->set_assemble_transformation(v->get_instance_transformation());
|
||||||
@@ -5564,14 +5572,6 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
|
|||||||
model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation());
|
model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (selection_mode == Selection::Volume) {
|
|
||||||
auto cur_mv = model_object->volumes[volume_idx];
|
|
||||||
if (cur_mv->get_transformation() != v->get_volume_transformation()) {
|
|
||||||
cur_mv->set_transformation(v->get_volume_transformation());
|
|
||||||
// BBS: backup
|
|
||||||
Slic3r::save_object_mesh(*model_object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object_moved = true;
|
object_moved = true;
|
||||||
model_object->invalidate_bounding_box();
|
model_object->invalidate_bounding_box();
|
||||||
|
|||||||
@@ -1305,7 +1305,7 @@ public:
|
|||||||
void set_tooltip(const std::string& tooltip);
|
void set_tooltip(const std::string& tooltip);
|
||||||
|
|
||||||
// the following methods add a snapshot to the undo/redo stack, unless the given string is empty
|
// the following methods add a snapshot to the undo/redo stack, unless the given string is empty
|
||||||
void do_move(const std::string& snapshot_type);
|
void do_move(const std::string& snapshot_type, bool force_volume_move = false);
|
||||||
void do_rotate(const std::string& snapshot_type);
|
void do_rotate(const std::string& snapshot_type);
|
||||||
void do_scale(const std::string& snapshot_type);
|
void do_scale(const std::string& snapshot_type);
|
||||||
void do_center();
|
void do_center();
|
||||||
|
|||||||
+113
-86
@@ -251,9 +251,11 @@
|
|||||||
#include "slic3r/GUI/ParamsDialog.hpp"
|
#include "slic3r/GUI/ParamsDialog.hpp"
|
||||||
#include "slic3r/GUI/ParamsPanel.hpp"
|
#include "slic3r/GUI/ParamsPanel.hpp"
|
||||||
#include "slic3r/GUI/PartPlate.hpp"
|
#include "slic3r/GUI/PartPlate.hpp"
|
||||||
|
#include "slic3r/GUI/TextureLibrary.hpp"
|
||||||
#include "slic3r/GUI/Widgets/Button.hpp"
|
#include "slic3r/GUI/Widgets/Button.hpp"
|
||||||
#include "slic3r/GUI/wxExtensions.hpp"
|
#include "slic3r/GUI/wxExtensions.hpp"
|
||||||
#include "slic3r/plugin/host/PluginPages.hpp"
|
#include "slic3r/plugin/host/PluginPages.hpp"
|
||||||
|
#include "slic3r/plugin/host/PluginResourceTextures.hpp"
|
||||||
#include <wx/defs.h>
|
#include <wx/defs.h>
|
||||||
#include "slic3r/GUI/Widgets/WebView.hpp"
|
#include "slic3r/GUI/Widgets/WebView.hpp"
|
||||||
#include <cwchar>
|
#include <cwchar>
|
||||||
@@ -1149,14 +1151,8 @@ void GUI_App::post_init()
|
|||||||
if(!m_networking_need_update && m_agent) {
|
if(!m_networking_need_update && m_agent) {
|
||||||
m_agent->set_on_ssdp_msg_fn(
|
m_agent->set_on_ssdp_msg_fn(
|
||||||
[this](std::string json_str) {
|
[this](std::string json_str) {
|
||||||
if (is_closing()) {
|
if (!is_closing() && m_device_manager)
|
||||||
return;
|
m_device_manager->on_machine_alive(json_str);
|
||||||
}
|
|
||||||
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) {
|
m_agent->set_on_http_error_fn([this](CloudEvent event, unsigned int status, std::string body) {
|
||||||
@@ -1814,14 +1810,8 @@ void GUI_App::restart_networking()
|
|||||||
init_networking_callbacks();
|
init_networking_callbacks();
|
||||||
m_agent->set_on_ssdp_msg_fn(
|
m_agent->set_on_ssdp_msg_fn(
|
||||||
[this](std::string json_str) {
|
[this](std::string json_str) {
|
||||||
if (is_closing()) {
|
if (!is_closing() && m_device_manager)
|
||||||
return;
|
m_device_manager->on_machine_alive(json_str);
|
||||||
}
|
|
||||||
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) {
|
m_agent->set_on_http_error_fn([this](CloudEvent event, unsigned int status, std::string body) {
|
||||||
@@ -2618,7 +2608,10 @@ void GUI_App::init_download_path()
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
fs::path dp(down_path);
|
fs::path dp(down_path);
|
||||||
if (!fs::exists(dp)) {
|
// 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)) {
|
||||||
|
|
||||||
std::string user_down_path = wxStandardPaths::Get().GetUserDir(wxStandardPaths::Dir_Downloads).ToUTF8().data();
|
std::string user_down_path = wxStandardPaths::Get().GetUserDir(wxStandardPaths::Dir_Downloads).ToUTF8().data();
|
||||||
app_config->set("download_path", user_down_path);
|
app_config->set("download_path", user_down_path);
|
||||||
@@ -3062,6 +3055,37 @@ void GUI_App::init_plugin_gui_wiring()
|
|||||||
refresh_plugins_dialog();
|
refresh_plugins_dialog();
|
||||||
switch_printer_agent_after_unload(capability.plugin_key);
|
switch_printer_agent_after_unload(capability.plugin_key);
|
||||||
});
|
});
|
||||||
|
plugin_mgr.subscribe_on_load_callback([](const std::string& plugin_key) {
|
||||||
|
if (wxTheApp == nullptr || wxGetApp().is_closing())
|
||||||
|
return;
|
||||||
|
PluginResourceTextures::instance().on_plugin_register(plugin_key);
|
||||||
|
Slic3r::GUI::bump_texture_library_generation();
|
||||||
|
});
|
||||||
|
plugin_mgr.subscribe_on_unload_callback([](const std::string& plugin_key) {
|
||||||
|
if (wxTheApp == nullptr || wxGetApp().is_closing())
|
||||||
|
return;
|
||||||
|
PluginResourceTextures::instance().on_plugin_deregister(plugin_key);
|
||||||
|
Slic3r::GUI::bump_texture_library_generation();
|
||||||
|
});
|
||||||
|
plugin_mgr.subscribe_on_capability_load_callback([](const PluginCapabilityId& capability) {
|
||||||
|
if (capability.type != PluginCapabilityType::ResourceTexture)
|
||||||
|
return;
|
||||||
|
if (wxTheApp == nullptr || wxGetApp().is_closing())
|
||||||
|
return;
|
||||||
|
PluginResourceTextures::instance().on_cap_register(capability);
|
||||||
|
Slic3r::GUI::bump_texture_library_generation();
|
||||||
|
});
|
||||||
|
plugin_mgr.subscribe_on_capability_unload_callback([](const PluginCapabilityId& capability) {
|
||||||
|
if (capability.type != PluginCapabilityType::ResourceTexture)
|
||||||
|
return;
|
||||||
|
if (wxTheApp == nullptr || wxGetApp().is_closing())
|
||||||
|
return;
|
||||||
|
PluginResourceTextures::instance().on_cap_deregister(capability);
|
||||||
|
Slic3r::GUI::bump_texture_library_generation();
|
||||||
|
});
|
||||||
|
// Packs already loaded before this wiring, or in tests where no callback fires.
|
||||||
|
PluginResourceTextures::instance().initial_sweep();
|
||||||
|
Slic3r::GUI::bump_texture_library_generation();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GUI_App::on_init_inner()
|
bool GUI_App::on_init_inner()
|
||||||
@@ -3925,81 +3949,74 @@ bool GUI_App::on_init_network(bool try_backup)
|
|||||||
if (config_version.empty()) {
|
if (config_version.empty()) {
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": no version configured, need to download";
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": no version configured, need to download";
|
||||||
m_networking_need_update = true;
|
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";
|
||||||
|
|
||||||
if (!m_device_manager)
|
std::string loaded_version = Slic3r::NetworkAgent::get_version();
|
||||||
m_device_manager = new Slic3r::DeviceManager();
|
if (app_config && !loaded_version.empty() && loaded_version != "00.00.00.00") {
|
||||||
if (!m_user_manager)
|
// Self-heal only when a genuinely different series loaded than configured (e.g. the
|
||||||
m_user_manager = new Slic3r::UserManager();
|
// 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
|
||||||
return false;
|
// 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();
|
||||||
int load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(false, config_version);
|
std::string loaded_series = network_plugin_series(loaded_version);
|
||||||
__retry:
|
if (network_plugin_series(config_version) != loaded_series) {
|
||||||
if (!load_agent_dll) {
|
std::string synced = is_series_managed_version(loaded_version) ? loaded_series : loaded_version;
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, load dll ok";
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": syncing config version from " << config_version
|
||||||
|
<< " to loaded " << loaded_version << " (stored as " << synced << ")";
|
||||||
std::string loaded_version = Slic3r::NetworkAgent::get_version();
|
app_config->set_network_plugin_version(synced);
|
||||||
if (app_config && !loaded_version.empty() && loaded_version != "00.00.00.00") {
|
app_config->save();
|
||||||
// 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()) {
|
if (check_networking_version()) {
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, compatibility version";
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, compatibility version";
|
||||||
auto bambu_source = Slic3r::NetworkAgent::get_bambu_source_entry();
|
auto bambu_source = Slic3r::NetworkAgent::get_bambu_source_entry();
|
||||||
if (!bambu_source) {
|
if (!bambu_source) {
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": can not get bambu source module!";
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": can not get bambu source module!";
|
||||||
m_networking_compatible = false;
|
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 (should_load_networking_plugin) {
|
if (should_load_networking_plugin) {
|
||||||
m_networking_need_update = true;
|
m_networking_need_update = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (try_backup) {
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, load dll failed";
|
||||||
int result = Slic3r::NetworkAgent::unload_network_module();
|
// A failed install can leave the config naming a build that never made it to disk;
|
||||||
BOOST_LOG_TRIVIAL(info) << "on_init_network, version mismatch, unload_network_module, result = " << result;
|
// fall back to the installed latest instead of dropping the user into the re-download
|
||||||
load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(true, config_version);
|
// flow. Only when the configured library is genuinely absent, though - a pinned series
|
||||||
try_backup = false;
|
// 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;
|
goto __retry;
|
||||||
}
|
}
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, version dismatch, need upload network module";
|
|
||||||
if (should_load_networking_plugin) {
|
if (should_load_networking_plugin) {
|
||||||
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, need upload network module";
|
||||||
m_networking_need_update = true;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4027,6 +4044,11 @@ bool GUI_App::on_init_network(bool try_backup)
|
|||||||
else
|
else
|
||||||
m_user_manager->set_agent(m_agent);
|
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 (this->is_enable_multi_machine()) {
|
||||||
if (!m_task_manager) {
|
if (!m_task_manager) {
|
||||||
m_task_manager = new Slic3r::TaskManager(m_agent);
|
m_task_manager = new Slic3r::TaskManager(m_agent);
|
||||||
@@ -4231,8 +4253,10 @@ void GUI_App::switch_printer_agent()
|
|||||||
const std::string print_host = config.opt_string("print_host");
|
const std::string print_host = config.opt_string("print_host");
|
||||||
if (!print_host.empty()) {
|
if (!print_host.empty()) {
|
||||||
const std::string dev_id = MachineObject::dev_id_from_address(print_host, config.opt_string("printhost_port"));
|
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();
|
MachineObject* sel = m_device_manager->get_selected_machine();
|
||||||
if (!sel || sel->get_dev_id() != dev_id)
|
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)
|
||||||
select_machine(effective_agent_id);
|
select_machine(effective_agent_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4274,6 +4298,9 @@ void GUI_App::select_machine(const std::string& agent_id)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::string port = host_cfg->opt_string("printhost_port");
|
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
|
// Generate dev_id from host and port
|
||||||
std::string dev_id = MachineObject::dev_id_from_address(print_host, port);
|
std::string dev_id = MachineObject::dev_id_from_address(print_host, port);
|
||||||
@@ -4300,11 +4327,6 @@ void GUI_App::select_machine(const std::string& agent_id)
|
|||||||
machine.dev_ip = dev_id;
|
machine.dev_ip = dev_id;
|
||||||
machine.dev_name = dev_id;
|
machine.dev_name = dev_id;
|
||||||
machine.printer_type = preset.config.opt_string("printer_model");
|
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(
|
existing = m_device_manager->insert_local_device(
|
||||||
machine, "lan", "free", "", access_code);
|
machine, "lan", "free", "", access_code);
|
||||||
@@ -4314,6 +4336,9 @@ void GUI_App::select_machine(const std::string& agent_id)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": created new machine dev_id=" << dev_id;
|
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://");
|
existing->local_use_ssl = boost::istarts_with(print_host, "https://");
|
||||||
|
|
||||||
@@ -10141,7 +10166,9 @@ void GUI_App::start_download(std::string url)
|
|||||||
}
|
}
|
||||||
//lets always init so if the download dest folder was changed, new dest is used
|
//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"));
|
boost::filesystem::path dest_folder(app_config->get("download_path"));
|
||||||
if (dest_folder.empty() || !boost::filesystem::is_directory(dest_folder)) {
|
// 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)) {
|
||||||
std::string msg = _u8L("Could not start URL download. Destination folder is not set. Please choose destination folder in Configuration Wizard.");
|
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;
|
BOOST_LOG_TRIVIAL(error) << msg;
|
||||||
show_error(nullptr, msg);
|
show_error(nullptr, msg);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include "Selection.hpp"
|
#include "Selection.hpp"
|
||||||
//BBS: add partplate related logic
|
//BBS: add partplate related logic
|
||||||
#include "PartPlate.hpp"
|
#include "PartPlate.hpp"
|
||||||
|
#include "Gizmos/GLGizmoAlignment.hpp"
|
||||||
#include "Gizmos/GLGizmoEmboss.hpp"
|
#include "Gizmos/GLGizmoEmboss.hpp"
|
||||||
#include "Gizmos/GLGizmoSVG.hpp"
|
#include "Gizmos/GLGizmoSVG.hpp"
|
||||||
|
|
||||||
@@ -1672,6 +1673,8 @@ void MenuFactory::create_extra_object_menu()
|
|||||||
append_menu_item_merge_parts_to_single_part(&m_object_menu);
|
append_menu_item_merge_parts_to_single_part(&m_object_menu);
|
||||||
// Object Center
|
// Object Center
|
||||||
append_menu_item_center(&m_object_menu);
|
append_menu_item_center(&m_object_menu);
|
||||||
|
// Object Align/Distribute
|
||||||
|
append_menu_item_align_distribute(&m_object_menu);
|
||||||
// Object Drop
|
// Object Drop
|
||||||
append_menu_item_drop(&m_object_menu);
|
append_menu_item_drop(&m_object_menu);
|
||||||
// Object Split
|
// Object Split
|
||||||
@@ -1779,6 +1782,7 @@ void MenuFactory::create_text_part_menu()
|
|||||||
append_menu_item_fix_through_cgal(menu);
|
append_menu_item_fix_through_cgal(menu);
|
||||||
append_menu_item_simplify(menu);
|
append_menu_item_simplify(menu);
|
||||||
append_menu_item_center(menu);
|
append_menu_item_center(menu);
|
||||||
|
append_menu_item_align_distribute(menu);
|
||||||
append_menu_items_mirror(menu);
|
append_menu_items_mirror(menu);
|
||||||
menu->AppendSeparator();
|
menu->AppendSeparator();
|
||||||
append_menu_item_per_object_process(menu);
|
append_menu_item_per_object_process(menu);
|
||||||
@@ -1811,6 +1815,7 @@ void MenuFactory::create_bbl_part_menu()
|
|||||||
append_menu_item_simplify(menu);
|
append_menu_item_simplify(menu);
|
||||||
append_menu_item_smooth_mesh(menu);
|
append_menu_item_smooth_mesh(menu);
|
||||||
append_menu_item_center(menu);
|
append_menu_item_center(menu);
|
||||||
|
append_menu_item_align_distribute(menu);
|
||||||
append_menu_item_drop(menu);
|
append_menu_item_drop(menu);
|
||||||
append_menu_items_mirror(menu);
|
append_menu_items_mirror(menu);
|
||||||
wxMenu* split_menu = new wxMenu();
|
wxMenu* split_menu = new wxMenu();
|
||||||
@@ -2136,6 +2141,7 @@ wxMenu* MenuFactory::multi_selection_menu()
|
|||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
append_menu_item_center(menu);
|
append_menu_item_center(menu);
|
||||||
|
append_menu_item_align_distribute(menu);
|
||||||
append_menu_item_drop(menu);
|
append_menu_item_drop(menu);
|
||||||
append_menu_item_fix_through_cgal(menu);
|
append_menu_item_fix_through_cgal(menu);
|
||||||
//append_menu_item_simplify(menu);
|
//append_menu_item_simplify(menu);
|
||||||
@@ -2161,6 +2167,7 @@ wxMenu* MenuFactory::multi_selection_menu()
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
append_menu_item_center(menu);
|
append_menu_item_center(menu);
|
||||||
|
append_menu_item_align_distribute(menu);
|
||||||
append_menu_item_drop(menu);
|
append_menu_item_drop(menu);
|
||||||
append_menu_item_fix_through_cgal(menu);
|
append_menu_item_fix_through_cgal(menu);
|
||||||
//append_menu_item_simplify(menu);
|
//append_menu_item_simplify(menu);
|
||||||
@@ -2325,6 +2332,247 @@ void MenuFactory::append_menu_item_center(wxMenu* menu)
|
|||||||
}, m_parent);
|
}, m_parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MenuFactory::append_menu_item_align_distribute(wxMenu *menu)
|
||||||
|
{
|
||||||
|
wxMenu* align_distribute_menu = new wxMenu();
|
||||||
|
if (!align_distribute_menu)
|
||||||
|
return;
|
||||||
|
|
||||||
|
append_menu_item(
|
||||||
|
align_distribute_menu, wxID_ANY, _L("Distribute left-right") + " (X)", "",
|
||||||
|
[](wxCommandEvent &) {
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||||
|
plater()->distribute_selection_x();
|
||||||
|
},
|
||||||
|
"", nullptr,
|
||||||
|
[]() {
|
||||||
|
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||||
|
return false;
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||||
|
return alignment_helper.can_distribute(GLGizmoAlignment::AlignType::DISTRIBUTE_X);
|
||||||
|
},
|
||||||
|
m_parent);
|
||||||
|
|
||||||
|
append_menu_item(
|
||||||
|
align_distribute_menu, wxID_ANY, _L("Distribute front-back") + " (Y)", "",
|
||||||
|
[](wxCommandEvent &) {
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||||
|
plater()->distribute_selection_y();
|
||||||
|
},
|
||||||
|
"", nullptr,
|
||||||
|
[]() {
|
||||||
|
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||||
|
return false;
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||||
|
return alignment_helper.can_distribute(GLGizmoAlignment::AlignType::DISTRIBUTE_Y);
|
||||||
|
},
|
||||||
|
m_parent);
|
||||||
|
|
||||||
|
append_menu_item(
|
||||||
|
align_distribute_menu, wxID_ANY, _L("Distribute top-bottom") + " (Z)", "",
|
||||||
|
[](wxCommandEvent &) {
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||||
|
plater()->distribute_selection_z();
|
||||||
|
},
|
||||||
|
"", nullptr,
|
||||||
|
[]() {
|
||||||
|
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||||
|
return false;
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||||
|
return alignment_helper.can_distribute(GLGizmoAlignment::AlignType::DISTRIBUTE_Z);
|
||||||
|
},
|
||||||
|
m_parent);
|
||||||
|
|
||||||
|
align_distribute_menu->AppendSeparator();
|
||||||
|
|
||||||
|
append_menu_item(
|
||||||
|
align_distribute_menu, wxID_ANY, _L("Align left") + " (-X)", "",
|
||||||
|
[](wxCommandEvent &) {
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||||
|
plater()->align_selection_x_min();
|
||||||
|
},
|
||||||
|
"", nullptr,
|
||||||
|
[]() {
|
||||||
|
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||||
|
return false;
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||||
|
return alignment_helper.can_align(GLGizmoAlignment::AlignType::X_MIN);
|
||||||
|
},
|
||||||
|
m_parent);
|
||||||
|
|
||||||
|
append_menu_item(
|
||||||
|
align_distribute_menu, wxID_ANY, _L("Align left-right center") + " (X)", "",
|
||||||
|
[](wxCommandEvent &) {
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||||
|
plater()->align_selection_x_center();
|
||||||
|
},
|
||||||
|
"", nullptr,
|
||||||
|
[]() {
|
||||||
|
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||||
|
return false;
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||||
|
return alignment_helper.can_align(GLGizmoAlignment::AlignType::CENTER_X);
|
||||||
|
},
|
||||||
|
m_parent);
|
||||||
|
|
||||||
|
append_menu_item(
|
||||||
|
align_distribute_menu, wxID_ANY, _L("Align right") + " (+X)", "",
|
||||||
|
[](wxCommandEvent &) {
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||||
|
plater()->align_selection_x_max();
|
||||||
|
},
|
||||||
|
"", nullptr,
|
||||||
|
[]() {
|
||||||
|
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||||
|
return false;
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||||
|
return alignment_helper.can_align(GLGizmoAlignment::AlignType::X_MAX);
|
||||||
|
},
|
||||||
|
m_parent);
|
||||||
|
|
||||||
|
align_distribute_menu->AppendSeparator();
|
||||||
|
|
||||||
|
append_menu_item(
|
||||||
|
align_distribute_menu, wxID_ANY, _L("Align front") + " (-Y)", "",
|
||||||
|
[](wxCommandEvent &) {
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||||
|
plater()->align_selection_y_min();
|
||||||
|
},
|
||||||
|
"", nullptr,
|
||||||
|
[]() {
|
||||||
|
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||||
|
return false;
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||||
|
return alignment_helper.can_align(GLGizmoAlignment::AlignType::Y_MIN);
|
||||||
|
},
|
||||||
|
m_parent);
|
||||||
|
|
||||||
|
append_menu_item(
|
||||||
|
align_distribute_menu, wxID_ANY, _L("Align front-back center") + " (Y)", "",
|
||||||
|
[](wxCommandEvent &) {
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||||
|
plater()->align_selection_y_center();
|
||||||
|
},
|
||||||
|
"", nullptr,
|
||||||
|
[]() {
|
||||||
|
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||||
|
return false;
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||||
|
return alignment_helper.can_align(GLGizmoAlignment::AlignType::CENTER_Y);
|
||||||
|
},
|
||||||
|
m_parent);
|
||||||
|
|
||||||
|
append_menu_item(
|
||||||
|
align_distribute_menu, wxID_ANY, _L("Align back") + " (+Y)", "",
|
||||||
|
[](wxCommandEvent &) {
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||||
|
plater()->align_selection_y_max();
|
||||||
|
},
|
||||||
|
"", nullptr,
|
||||||
|
[]() {
|
||||||
|
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||||
|
return false;
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||||
|
return alignment_helper.can_align(GLGizmoAlignment::AlignType::Y_MAX);
|
||||||
|
},
|
||||||
|
m_parent);
|
||||||
|
|
||||||
|
align_distribute_menu->AppendSeparator();
|
||||||
|
|
||||||
|
append_menu_item(
|
||||||
|
align_distribute_menu, wxID_ANY, _L("Align bottom") + " (-Z)", "",
|
||||||
|
[](wxCommandEvent &) {
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||||
|
plater()->align_selection_z_min();
|
||||||
|
},
|
||||||
|
"", nullptr,
|
||||||
|
[]() {
|
||||||
|
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||||
|
return false;
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||||
|
return alignment_helper.can_align(GLGizmoAlignment::AlignType::Z_MIN);
|
||||||
|
},
|
||||||
|
m_parent);
|
||||||
|
|
||||||
|
append_menu_item(
|
||||||
|
align_distribute_menu, wxID_ANY, _L("Align top-bottom center") + " (Z)", "",
|
||||||
|
[](wxCommandEvent &) {
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||||
|
plater()->align_selection_z_center();
|
||||||
|
},
|
||||||
|
"", nullptr,
|
||||||
|
[]() {
|
||||||
|
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||||
|
return false;
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||||
|
return alignment_helper.can_align(GLGizmoAlignment::AlignType::CENTER_Z);
|
||||||
|
},
|
||||||
|
m_parent);
|
||||||
|
|
||||||
|
append_menu_item(
|
||||||
|
align_distribute_menu, wxID_ANY, _L("Align top") + " (+Z)", "",
|
||||||
|
[](wxCommandEvent &) {
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||||
|
plater()->align_selection_z_max();
|
||||||
|
},
|
||||||
|
"", nullptr,
|
||||||
|
[]() {
|
||||||
|
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||||
|
return false;
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||||
|
return alignment_helper.can_align(GLGizmoAlignment::AlignType::Z_MAX);
|
||||||
|
},
|
||||||
|
m_parent);
|
||||||
|
|
||||||
|
append_submenu(menu, align_distribute_menu, wxID_ANY, _L("Align/Distribute"), _L("Align and distribute objects"), "",
|
||||||
|
[]() {
|
||||||
|
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||||
|
return false;
|
||||||
|
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||||
|
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||||
|
|
||||||
|
bool has_any_align = alignment_helper.can_align(GLGizmoAlignment::AlignType::CENTER_X) ||
|
||||||
|
alignment_helper.can_align(GLGizmoAlignment::AlignType::CENTER_Y) ||
|
||||||
|
alignment_helper.can_align(GLGizmoAlignment::AlignType::CENTER_Z) ||
|
||||||
|
alignment_helper.can_align(GLGizmoAlignment::AlignType::X_MAX) ||
|
||||||
|
alignment_helper.can_align(GLGizmoAlignment::AlignType::X_MIN) ||
|
||||||
|
alignment_helper.can_align(GLGizmoAlignment::AlignType::Y_MAX) ||
|
||||||
|
alignment_helper.can_align(GLGizmoAlignment::AlignType::Y_MIN) ||
|
||||||
|
alignment_helper.can_align(GLGizmoAlignment::AlignType::Z_MAX) ||
|
||||||
|
alignment_helper.can_align(GLGizmoAlignment::AlignType::Z_MIN);
|
||||||
|
|
||||||
|
bool has_any_distribute = alignment_helper.can_distribute(GLGizmoAlignment::AlignType::DISTRIBUTE_X) ||
|
||||||
|
alignment_helper.can_distribute(GLGizmoAlignment::AlignType::DISTRIBUTE_Y) ||
|
||||||
|
alignment_helper.can_distribute(GLGizmoAlignment::AlignType::DISTRIBUTE_Z);
|
||||||
|
|
||||||
|
return has_any_align || has_any_distribute;
|
||||||
|
}, m_parent);
|
||||||
|
}
|
||||||
|
|
||||||
void MenuFactory::append_menu_item_drop(wxMenu* menu)
|
void MenuFactory::append_menu_item_drop(wxMenu* menu)
|
||||||
{
|
{
|
||||||
append_menu_item(menu, wxID_ANY, _L("Drop") , "",
|
append_menu_item(menu, wxID_ANY, _L("Drop") , "",
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ private:
|
|||||||
void append_menu_item_merge_to_single_object(wxMenu* menu);
|
void append_menu_item_merge_to_single_object(wxMenu* menu);
|
||||||
void append_menu_item_merge_parts_to_single_part(wxMenu *menu);
|
void append_menu_item_merge_parts_to_single_part(wxMenu *menu);
|
||||||
void append_menu_items_mirror(wxMenu *menu);
|
void append_menu_items_mirror(wxMenu *menu);
|
||||||
|
void append_menu_item_align_distribute(wxMenu *menu);
|
||||||
void append_menu_item_precise_seam_submenu(wxMenu* menu);
|
void append_menu_item_precise_seam_submenu(wxMenu* menu);
|
||||||
void append_menu_item_invalidate_cut_info(wxMenu *menu);
|
void append_menu_item_invalidate_cut_info(wxMenu *menu);
|
||||||
void append_menu_item_edit_text(wxMenu *menu);
|
void append_menu_item_edit_text(wxMenu *menu);
|
||||||
|
|||||||
@@ -0,0 +1,694 @@
|
|||||||
|
#include "GLGizmoAlignment.hpp"
|
||||||
|
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||||
|
#include "slic3r/GUI/GUI_App.hpp"
|
||||||
|
#include "slic3r/GUI/Plater.hpp"
|
||||||
|
#include "libslic3r/Model.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace GUI {
|
||||||
|
|
||||||
|
GLGizmoAlignment::GLGizmoAlignment(GLCanvas3D& canvas) : m_canvas(canvas)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLGizmoAlignment::align_objects(AlignType type, bool align_parent)
|
||||||
|
{
|
||||||
|
if (!validate_selection_for_align()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case AlignType::CENTER_X: return align_to_center(type, align_parent);
|
||||||
|
case AlignType::CENTER_Y: return align_to_center(type, align_parent);
|
||||||
|
case AlignType::CENTER_Z: return align_to_center(type, align_parent);
|
||||||
|
case AlignType::Y_MAX: return align_to_y_max(align_parent);
|
||||||
|
case AlignType::Y_MIN: return align_to_y_min(align_parent);
|
||||||
|
case AlignType::X_MAX: return align_to_x_max(align_parent);
|
||||||
|
case AlignType::X_MIN: return align_to_x_min(align_parent);
|
||||||
|
case AlignType::Z_MAX: return align_to_z_max(align_parent);
|
||||||
|
case AlignType::Z_MIN: return align_to_z_min(align_parent);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLGizmoAlignment::distribute_objects(AlignType type)
|
||||||
|
{
|
||||||
|
if (!can_distribute(type)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case AlignType::DISTRIBUTE_X:
|
||||||
|
return distribute_x();
|
||||||
|
case AlignType::DISTRIBUTE_Y:
|
||||||
|
return distribute_y();
|
||||||
|
case AlignType::DISTRIBUTE_Z:
|
||||||
|
return distribute_z();
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLGizmoAlignment::align_to_y_max(bool align_parent)
|
||||||
|
{
|
||||||
|
return align_objects_generic(
|
||||||
|
[](const ObjectInfo& obj) { return obj.bbox.max.y(); },
|
||||||
|
[](Vec3d& displacement, double target, double current_max) {
|
||||||
|
displacement.y() = target - current_max;
|
||||||
|
}, "Align Y Max", align_parent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLGizmoAlignment::align_to_y_min(bool align_parent)
|
||||||
|
{
|
||||||
|
return align_objects_generic(
|
||||||
|
[](const ObjectInfo& obj) { return obj.bbox.min.y(); },
|
||||||
|
[](Vec3d& displacement, double target, double current_min) {
|
||||||
|
displacement.y() = target - current_min;
|
||||||
|
}, "Align Y Min", align_parent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLGizmoAlignment::align_to_x_max(bool align_parent)
|
||||||
|
{
|
||||||
|
return align_objects_generic(
|
||||||
|
[](const ObjectInfo& obj) { return obj.bbox.max.x(); },
|
||||||
|
[](Vec3d& displacement, double target, double current_max) {
|
||||||
|
displacement.x() = target - current_max;
|
||||||
|
}, "Align X Max", align_parent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLGizmoAlignment::align_to_x_min(bool align_parent)
|
||||||
|
{
|
||||||
|
return align_objects_generic(
|
||||||
|
[](const ObjectInfo& obj) { return obj.bbox.min.x(); },
|
||||||
|
[](Vec3d& displacement, double target, double current_min) {
|
||||||
|
displacement.x() = target - current_min;
|
||||||
|
}, "Align X Min", align_parent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLGizmoAlignment::align_to_center(AlignType type,bool align_parent)
|
||||||
|
{
|
||||||
|
const Selection& selection = get_selection();
|
||||||
|
std::string type_str = type == AlignType::CENTER_X ? "X" : (type == AlignType::CENTER_Y ? "Y" : "Z");
|
||||||
|
std::string operation_name = type == AlignType::CENTER_X ? "X Center" : (type == AlignType::CENTER_Y ? "Y Center" : "Z Center");
|
||||||
|
double selection_center = 0.f;
|
||||||
|
if (align_parent) {
|
||||||
|
selection_center = get_current_coord(operation_name, m_parent_box);
|
||||||
|
|
||||||
|
// Handle multiple volume selection
|
||||||
|
if (is_part_align_parent()) {
|
||||||
|
if (!take_snapshot("Align to parent center " + type_str)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_selection().setup_cache();
|
||||||
|
|
||||||
|
auto selection_bbox = selection.get_bounding_box();
|
||||||
|
double current_coord = get_current_coord(operation_name, selection_bbox);
|
||||||
|
Vec3d displacement = generate_displacement(operation_name, selection_center - current_coord);
|
||||||
|
// Move each selected volume to align to the center of their collective bounding box
|
||||||
|
for (unsigned int idx : selection.get_volume_idxs()) {
|
||||||
|
const GLVolume *volume = selection.get_volume(idx);
|
||||||
|
if (volume) {
|
||||||
|
get_selection().translate(volume->object_idx(), volume->instance_idx(), volume->volume_idx(), displacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finish_operation("Align to parent center " + type_str);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
BoundingBoxf3 big_bb;
|
||||||
|
auto objects = get_selected_objects_info(big_bb);
|
||||||
|
if (objects.empty()) return false;
|
||||||
|
// Handle multiple objects or parts selection
|
||||||
|
|
||||||
|
BoundingBoxf3 selection_bbox = selection.get_bounding_box();
|
||||||
|
if (!take_snapshot("Align to parent center " + type_str)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_selection().setup_cache();
|
||||||
|
double current_coord = get_current_coord(operation_name, big_bb);
|
||||||
|
Vec3d displacement = generate_displacement(operation_name, selection_center - current_coord);
|
||||||
|
// Multiple objects: use selection bounding box for alignment
|
||||||
|
for (const auto &obj : objects) {
|
||||||
|
apply_transformation(obj.object_idx, obj.instance_idx, displacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
finish_operation("Align to parent center " + type_str);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Handle single object selection// Handle multiple volume selection
|
||||||
|
if (selection.is_single_full_object() ||
|
||||||
|
selection.is_multiple_volume() || selection.is_multiple_modifier() || selection.is_single_volume() || selection.is_single_modifier()) {
|
||||||
|
if (!take_snapshot("Align Parts Center " + type_str)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_selection().setup_cache();
|
||||||
|
|
||||||
|
BoundingBoxf3 selection_bbox = selection.get_bounding_box();
|
||||||
|
selection_center = get_current_coord(operation_name, selection_bbox);
|
||||||
|
|
||||||
|
// Move each selected volume to align to the center of their collective bounding box
|
||||||
|
for (unsigned int idx : selection.get_volume_idxs()) {
|
||||||
|
const GLVolume* volume = selection.get_volume(idx);
|
||||||
|
if (volume) {
|
||||||
|
auto temp_box = volume->transformed_convex_hull_bounding_box();
|
||||||
|
double current = get_current_coord(operation_name, temp_box);
|
||||||
|
double offset = selection_center - current;
|
||||||
|
Vec3d displacement = generate_displacement(operation_name, offset);
|
||||||
|
get_selection().translate(volume->object_idx(), volume->instance_idx(), volume->volume_idx(), displacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finish_operation("Align Parts Center" + type_str, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
BoundingBoxf3 big_bb;
|
||||||
|
auto objects = get_selected_objects_info(big_bb);
|
||||||
|
if (objects.empty()) return false;
|
||||||
|
// Handle multiple objects or parts selection
|
||||||
|
if (selection.is_multiple_full_object()) {
|
||||||
|
BoundingBoxf3 selection_bbox = selection.get_bounding_box();
|
||||||
|
double selection_center = get_current_coord(operation_name, selection_bbox);
|
||||||
|
|
||||||
|
if (!take_snapshot("Align Objects Center" + type_str)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_selection().setup_cache();
|
||||||
|
// Multiple objects: use selection bounding box for alignment
|
||||||
|
for (const auto& obj : objects) {
|
||||||
|
BoundingBoxf3 temp_bbox = obj.bbox;
|
||||||
|
double current_coord = get_current_coord(operation_name, temp_bbox);
|
||||||
|
Vec3d displacement = generate_displacement(operation_name, selection_center - current_coord);
|
||||||
|
apply_transformation(obj.object_idx, obj.instance_idx, displacement);
|
||||||
|
}
|
||||||
|
finish_operation("Align Objects Center" + type_str);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLGizmoAlignment::align_to_z_max(bool align_parent)
|
||||||
|
{
|
||||||
|
return align_objects_generic(
|
||||||
|
[](const ObjectInfo& obj) { return obj.bbox.max.z(); },
|
||||||
|
[](Vec3d& displacement, double target, double current_max) {
|
||||||
|
displacement.z() = target - current_max;
|
||||||
|
}, "Align Z Max", align_parent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLGizmoAlignment::align_to_z_min(bool align_parent)
|
||||||
|
{
|
||||||
|
return align_objects_generic(
|
||||||
|
[](const ObjectInfo& obj) { return obj.bbox.min.z(); },
|
||||||
|
[](Vec3d& displacement, double target, double current_min) {
|
||||||
|
displacement.z() = target - current_min;
|
||||||
|
}, "Align Z Min", align_parent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLGizmoAlignment::distribute_y()
|
||||||
|
{
|
||||||
|
return distribute_objects_generic(
|
||||||
|
[](const ObjectInfo& obj) { return obj.center.y(); },
|
||||||
|
1,
|
||||||
|
"Distribute Y"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLGizmoAlignment::distribute_x()
|
||||||
|
{
|
||||||
|
return distribute_objects_generic(
|
||||||
|
[](const ObjectInfo& obj) { return obj.center.x(); },
|
||||||
|
0,
|
||||||
|
"Distribute X"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLGizmoAlignment::distribute_z()
|
||||||
|
{
|
||||||
|
return distribute_objects_generic(
|
||||||
|
[](const ObjectInfo& obj) { return obj.center.z(); },
|
||||||
|
2,
|
||||||
|
"Distribute Z"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename GetCoordFunc, typename SetCoordFunc>
|
||||||
|
bool GLGizmoAlignment::align_objects_generic(GetCoordFunc get_coord, SetCoordFunc set_coord, const std::string &operation_name ,bool align_parent)
|
||||||
|
{
|
||||||
|
const Selection& selection = get_selection();
|
||||||
|
if (align_parent) {
|
||||||
|
// Find reference coordinate
|
||||||
|
double reference_coord = get_current_coord(operation_name, m_parent_box);
|
||||||
|
// Handle parts selection differently
|
||||||
|
if (is_part_align_parent()) {
|
||||||
|
if (!take_snapshot("align to parent node " + operation_name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_selection().setup_cache();
|
||||||
|
|
||||||
|
// Get all selected volumes
|
||||||
|
std::vector<const GLVolume *> volumes;
|
||||||
|
for (unsigned int idx : selection.get_volume_idxs()) {
|
||||||
|
const GLVolume *volume = selection.get_volume(idx);
|
||||||
|
if (volume) { volumes.push_back(volume); }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (volumes.empty())
|
||||||
|
return false;
|
||||||
|
BoundingBoxf3 selection_bbox = selection.get_bounding_box();
|
||||||
|
double current_coord = get_current_coord(operation_name, selection_bbox);
|
||||||
|
Vec3d displacement = generate_displacement(operation_name, reference_coord - current_coord);
|
||||||
|
for (const GLVolume *volume : volumes) {
|
||||||
|
if (displacement.norm() > 1e-6) {
|
||||||
|
get_selection().translate(volume->object_idx(), volume->instance_idx(), volume->volume_idx(), displacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finish_operation("align to parent node " + operation_name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle objects selection (original logic)
|
||||||
|
BoundingBoxf3 big_bb;
|
||||||
|
auto objects = get_selected_objects_info(big_bb);
|
||||||
|
if (objects.empty()) return false;
|
||||||
|
|
||||||
|
if (!take_snapshot("align to parent node " + operation_name)) { return false; }
|
||||||
|
|
||||||
|
get_selection().setup_cache();
|
||||||
|
|
||||||
|
double current_coord = get_current_coord(operation_name, big_bb);
|
||||||
|
Vec3d displacement = generate_displacement(operation_name, reference_coord - current_coord);
|
||||||
|
|
||||||
|
for (const auto &obj : objects) {
|
||||||
|
if (displacement.norm() > 1e-6) {
|
||||||
|
apply_transformation(obj.object_idx, obj.instance_idx, displacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finish_operation("align to parent node " + operation_name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Handle parts selection differently
|
||||||
|
if (selection.is_single_full_object() || selection.is_multiple_volume() || selection.is_multiple_modifier()) {
|
||||||
|
if (!take_snapshot(operation_name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_selection().setup_cache();
|
||||||
|
|
||||||
|
// Get all selected volumes
|
||||||
|
std::vector<const GLVolume*> volumes;
|
||||||
|
for (unsigned int idx : selection.get_volume_idxs()) {
|
||||||
|
const GLVolume* volume = selection.get_volume(idx);
|
||||||
|
if (volume) {
|
||||||
|
volumes.push_back(volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (volumes.empty()) return false;
|
||||||
|
|
||||||
|
// Find reference coordinate
|
||||||
|
double reference_coord = 0.0;
|
||||||
|
bool first = true;
|
||||||
|
|
||||||
|
for (const GLVolume* volume : volumes) {
|
||||||
|
BoundingBoxf3 bbox = volume->transformed_convex_hull_bounding_box();
|
||||||
|
double coord = 0.0;
|
||||||
|
|
||||||
|
if (operation_name.find("X Max") != std::string::npos) {
|
||||||
|
coord = bbox.max.x();
|
||||||
|
} else if (operation_name.find("X Min") != std::string::npos) {
|
||||||
|
coord = bbox.min.x();
|
||||||
|
} else if (operation_name.find("Y Max") != std::string::npos) {
|
||||||
|
coord = bbox.max.y();
|
||||||
|
} else if (operation_name.find("Y Min") != std::string::npos) {
|
||||||
|
coord = bbox.min.y();
|
||||||
|
} else if (operation_name.find("Z Max") != std::string::npos) {
|
||||||
|
coord = bbox.max.z();
|
||||||
|
} else if (operation_name.find("Z Min") != std::string::npos) {
|
||||||
|
coord = bbox.min.z();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (first) {
|
||||||
|
reference_coord = coord;
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
if (operation_name.find("Max") != std::string::npos) {
|
||||||
|
reference_coord = std::max(reference_coord, coord);
|
||||||
|
} else if (operation_name.find("Min") != std::string::npos) {
|
||||||
|
reference_coord = std::min(reference_coord, coord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const GLVolume* volume : volumes) {
|
||||||
|
BoundingBoxf3 bbox = volume->transformed_convex_hull_bounding_box();
|
||||||
|
double current_coord = 0.0;
|
||||||
|
|
||||||
|
if (operation_name.find("X Max") != std::string::npos) {
|
||||||
|
current_coord = bbox.max.x();
|
||||||
|
} else if (operation_name.find("X Min") != std::string::npos) {
|
||||||
|
current_coord = bbox.min.x();
|
||||||
|
} else if (operation_name.find("Y Max") != std::string::npos) {
|
||||||
|
current_coord = bbox.max.y();
|
||||||
|
} else if (operation_name.find("Y Min") != std::string::npos) {
|
||||||
|
current_coord = bbox.min.y();
|
||||||
|
} else if (operation_name.find("Z Max") != std::string::npos) {
|
||||||
|
current_coord = bbox.max.z();
|
||||||
|
} else if (operation_name.find("Z Min") != std::string::npos) {
|
||||||
|
current_coord = bbox.min.z();
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3d displacement = Vec3d::Zero();
|
||||||
|
|
||||||
|
if (operation_name.find("X") != std::string::npos) {
|
||||||
|
displacement.x() = reference_coord - current_coord;
|
||||||
|
} else if (operation_name.find("Y") != std::string::npos) {
|
||||||
|
displacement.y() = reference_coord - current_coord;
|
||||||
|
} else if (operation_name.find("Z") != std::string::npos) {
|
||||||
|
displacement.z() = reference_coord - current_coord;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (displacement.norm() > 1e-6) {
|
||||||
|
get_selection().translate(volume->object_idx(), volume->instance_idx(), volume->volume_idx(), displacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finish_operation(operation_name,true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle objects selection (original logic)
|
||||||
|
BoundingBoxf3 big_bb;
|
||||||
|
auto objects = get_selected_objects_info(big_bb);
|
||||||
|
if (objects.empty()) return false;
|
||||||
|
|
||||||
|
if (!take_snapshot(operation_name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_selection().setup_cache();
|
||||||
|
|
||||||
|
double reference_coord = get_coord(objects[0]);
|
||||||
|
for (const auto& obj : objects) {
|
||||||
|
double coord = get_coord(obj);
|
||||||
|
if (operation_name.find("Max") != std::string::npos) {
|
||||||
|
reference_coord = std::max(reference_coord, coord);
|
||||||
|
} else if (operation_name.find("Min") != std::string::npos) {
|
||||||
|
reference_coord = std::min(reference_coord, coord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& obj : objects) {
|
||||||
|
double current_coord = get_coord(obj);
|
||||||
|
Vec3d displacement = Vec3d::Zero();
|
||||||
|
set_coord(displacement, reference_coord, current_coord);
|
||||||
|
|
||||||
|
if (displacement.norm() > 1e-6) {
|
||||||
|
apply_transformation(obj.object_idx, obj.instance_idx, displacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finish_operation(operation_name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename GetCoordFunc>
|
||||||
|
bool GLGizmoAlignment::distribute_objects_generic(GetCoordFunc get_coord, int axis, const std::string& operation_name)
|
||||||
|
{
|
||||||
|
const Selection& selection = get_selection();
|
||||||
|
|
||||||
|
if (selection.is_single_full_object() || selection.is_multiple_volume() || selection.is_multiple_modifier()) {
|
||||||
|
std::vector<const GLVolume*> volumes;
|
||||||
|
for (unsigned int idx : selection.get_volume_idxs()) {
|
||||||
|
const GLVolume* v = selection.get_volume(idx);
|
||||||
|
if (v) volumes.push_back(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (volumes.size() < 3) return false;
|
||||||
|
if (!take_snapshot(operation_name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
get_selection().setup_cache();
|
||||||
|
|
||||||
|
struct VolEntry { const GLVolume* v; double coord; };
|
||||||
|
std::vector<VolEntry> entries;
|
||||||
|
entries.reserve(volumes.size());
|
||||||
|
for (const GLVolume* v : volumes) {
|
||||||
|
BoundingBoxf3 bbox = v->transformed_convex_hull_bounding_box();
|
||||||
|
Vec3d c = bbox.center();
|
||||||
|
double coord = (axis == 0 ? c.x() : axis == 1 ? c.y() : c.z());
|
||||||
|
entries.push_back({v, coord});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(entries.begin(), entries.end(), [](const VolEntry& a, const VolEntry& b){ return a.coord < b.coord; });
|
||||||
|
|
||||||
|
BoundingBoxf3 selection_bbox = selection.get_bounding_box();
|
||||||
|
BoundingBoxf3 left_bbox = entries.front().v->transformed_convex_hull_bounding_box();
|
||||||
|
BoundingBoxf3 right_bbox = entries.back().v->transformed_convex_hull_bounding_box();
|
||||||
|
auto axis_extent = [&](const BoundingBoxf3 &bb) { return (axis == 0 ? (bb.max.x() - bb.min.x()) : axis == 1 ? (bb.max.y() - bb.min.y()) : (bb.max.z() - bb.min.z())); };
|
||||||
|
double left_width = axis_extent(left_bbox);
|
||||||
|
double right_width = axis_extent(right_bbox);
|
||||||
|
double bbox_min = (axis == 0 ? selection_bbox.min.x() : axis == 1 ? selection_bbox.min.y() : selection_bbox.min.z());
|
||||||
|
double bbox_max = (axis == 0 ? selection_bbox.max.x() : axis == 1 ? selection_bbox.max.y() : selection_bbox.max.z());
|
||||||
|
double min_center = bbox_min + left_width * 0.5;
|
||||||
|
double max_center = bbox_max - right_width * 0.5;
|
||||||
|
double total_distance = max_center - min_center;
|
||||||
|
double interval = (entries.size() > 1) ? total_distance / (entries.size() - 1) : 0;
|
||||||
|
for (size_t i = 1; i < entries.size() - 1; ++i) {
|
||||||
|
double target_coord = min_center + i * interval;
|
||||||
|
double current_coord = entries[i].coord;
|
||||||
|
double offset = target_coord - current_coord;
|
||||||
|
|
||||||
|
if (std::abs(offset) > 1e-6) {
|
||||||
|
Vec3d displacement = Vec3d::Zero();
|
||||||
|
displacement[axis] = offset;
|
||||||
|
const GLVolume *v = entries[i].v;
|
||||||
|
get_selection().translate(v->object_idx(), v->instance_idx(), v->volume_idx(), displacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finish_operation(operation_name,true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
BoundingBoxf3 big_bb;
|
||||||
|
auto objects = get_selected_objects_info(big_bb);
|
||||||
|
if (objects.size() < 3) return false;
|
||||||
|
|
||||||
|
if (!take_snapshot(operation_name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_selection().setup_cache();
|
||||||
|
|
||||||
|
std::sort(objects.begin(), objects.end(),
|
||||||
|
[&get_coord](const ObjectInfo& a, const ObjectInfo& b) {
|
||||||
|
return get_coord(a) < get_coord(b);
|
||||||
|
});
|
||||||
|
|
||||||
|
double min_coord = get_coord(objects.front());
|
||||||
|
double max_coord = get_coord(objects.back());
|
||||||
|
double total_distance = max_coord - min_coord;
|
||||||
|
double interval = (objects.size() > 1) ? total_distance / (objects.size() - 1) : 0;
|
||||||
|
for (size_t i = 1; i < objects.size() - 1; ++i) {
|
||||||
|
double target_coord = min_coord + i * interval;
|
||||||
|
double current_coord = get_coord(objects[i]);
|
||||||
|
double offset = target_coord - current_coord;
|
||||||
|
|
||||||
|
if (std::abs(offset) > 1e-6) {
|
||||||
|
Vec3d displacement = Vec3d::Zero();
|
||||||
|
displacement[axis] = offset;
|
||||||
|
apply_transformation(objects[i].object_idx, objects[i].instance_idx, displacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finish_operation(operation_name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLGizmoAlignment::can_align(AlignType type) const
|
||||||
|
{
|
||||||
|
const Selection& selection = get_selection();
|
||||||
|
|
||||||
|
bool is_single_object = selection.is_single_full_object();
|
||||||
|
bool is_multiple_objects = selection.is_multiple_full_object();
|
||||||
|
bool is_single_part = selection.is_single_volume() || selection.is_single_modifier();
|
||||||
|
bool is_multiple_parts = selection.is_multiple_volume() || selection.is_multiple_modifier();
|
||||||
|
|
||||||
|
|
||||||
|
return validate_selection_for_align();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLGizmoAlignment::can_distribute(AlignType type) const
|
||||||
|
{
|
||||||
|
const Selection& selection = get_selection();
|
||||||
|
if (selection.is_single_full_object()) {
|
||||||
|
size_t count = selection.get_volume_idxs().size();
|
||||||
|
if (count >= 3)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (selection.is_single_volume() ||
|
||||||
|
selection.is_single_modifier()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selection.is_multiple_volume() || selection.is_multiple_modifier()) {
|
||||||
|
size_t count = selection.get_volume_idxs().size();
|
||||||
|
if (count < 3) return false;
|
||||||
|
return (type == AlignType::DISTRIBUTE_X ||
|
||||||
|
type == AlignType::DISTRIBUTE_Y ||
|
||||||
|
type == AlignType::DISTRIBUTE_Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selection.is_multiple_full_object()) {
|
||||||
|
BoundingBoxf3 big_bb;
|
||||||
|
auto objects = get_selected_objects_info(big_bb);
|
||||||
|
if (objects.size() < 3) return false;
|
||||||
|
return (type == AlignType::DISTRIBUTE_X || type == AlignType::DISTRIBUTE_Y || type == AlignType::DISTRIBUTE_Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
return validate_selection_for_distribute();
|
||||||
|
}
|
||||||
|
|
||||||
|
double GLGizmoAlignment::get_current_coord(std::string operation_name, BoundingBoxf3 &bbox)
|
||||||
|
{
|
||||||
|
double current_coord = 0.0;
|
||||||
|
if (operation_name.find("X Max") != std::string::npos) {
|
||||||
|
current_coord = bbox.max.x();
|
||||||
|
} else if (operation_name.find("X Min") != std::string::npos) {
|
||||||
|
current_coord = bbox.min.x();
|
||||||
|
} else if (operation_name.find("Y Max") != std::string::npos) {
|
||||||
|
current_coord = bbox.max.y();
|
||||||
|
} else if (operation_name.find("Y Min") != std::string::npos) {
|
||||||
|
current_coord = bbox.min.y();
|
||||||
|
} else if (operation_name.find("Z Max") != std::string::npos) {
|
||||||
|
current_coord = bbox.max.z();
|
||||||
|
} else if (operation_name.find("Z Min") != std::string::npos) {
|
||||||
|
current_coord = bbox.min.z();
|
||||||
|
} else if (operation_name.find("X Center") != std::string::npos) {
|
||||||
|
current_coord = bbox.center().x();
|
||||||
|
} else if (operation_name.find("Y Center") != std::string::npos) {
|
||||||
|
current_coord = bbox.center().y();
|
||||||
|
} else if (operation_name.find("Z Center") != std::string::npos) {
|
||||||
|
current_coord = bbox.center().z();
|
||||||
|
}
|
||||||
|
return current_coord;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3d GLGizmoAlignment::generate_displacement(std::string operation_name, double offset) {
|
||||||
|
Vec3d displacement = Vec3d::Zero();
|
||||||
|
if (operation_name.find("X") != std::string::npos) {
|
||||||
|
displacement.x() = offset;
|
||||||
|
} else if (operation_name.find("Y") != std::string::npos) {
|
||||||
|
displacement.y() = offset;
|
||||||
|
} else if (operation_name.find("Z") != std::string::npos) {
|
||||||
|
displacement.z() = offset;
|
||||||
|
}
|
||||||
|
return displacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLGizmoAlignment::is_part_align_parent()
|
||||||
|
{
|
||||||
|
Selection &selection = get_selection();
|
||||||
|
if (selection.is_multiple_volume() || selection.is_multiple_modifier() || selection.is_single_volume() || selection.is_single_modifier()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<GLGizmoAlignment::ObjectInfo> GLGizmoAlignment::get_selected_objects_info(BoundingBoxf3 &big_bb) const
|
||||||
|
{
|
||||||
|
std::vector<ObjectInfo> objects;
|
||||||
|
Selection& selection = get_selection();
|
||||||
|
|
||||||
|
if (selection.is_empty()) return objects;
|
||||||
|
|
||||||
|
const auto& content = selection.get_content();
|
||||||
|
for (const auto& obj_it : content) {
|
||||||
|
for (const auto& inst_idx : obj_it.second) {
|
||||||
|
int obj_idx = obj_it.first;
|
||||||
|
|
||||||
|
if (obj_idx >= 0 && obj_idx < selection.get_model()->objects.size()) {
|
||||||
|
ModelObject* object = selection.get_model()->objects[obj_idx];
|
||||||
|
if (inst_idx >= 0 && inst_idx < object->instances.size()) {
|
||||||
|
BoundingBoxf3 bbox = object->instance_bounding_box(inst_idx);
|
||||||
|
big_bb.merge(bbox);
|
||||||
|
objects.emplace_back(obj_idx, inst_idx, bbox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLGizmoAlignment::set_parent_box(const BoundingBoxf3 &bb) {
|
||||||
|
m_parent_box = bb;
|
||||||
|
}
|
||||||
|
|
||||||
|
Selection& GLGizmoAlignment::get_selection() const
|
||||||
|
{
|
||||||
|
return m_canvas.get_selection();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLGizmoAlignment::take_snapshot(const std::string& name)
|
||||||
|
{
|
||||||
|
wxGetApp().plater()->take_snapshot(name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLGizmoAlignment::apply_transformation(int obj_idx, int inst_idx, const Vec3d& displacement)
|
||||||
|
{
|
||||||
|
get_selection().translate(obj_idx, inst_idx, displacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLGizmoAlignment::finish_operation(const std::string &operation_name, bool force_volume_move)
|
||||||
|
{
|
||||||
|
get_selection().notify_instance_update(-1, -1);
|
||||||
|
m_canvas.do_move(operation_name, force_volume_move);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLGizmoAlignment::validate_selection_for_align() const
|
||||||
|
{
|
||||||
|
const Selection& selection = get_selection();
|
||||||
|
|
||||||
|
if (selection.is_single_full_object() ||
|
||||||
|
selection.is_single_volume() ||
|
||||||
|
selection.is_single_modifier()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return selection.get_volume_idxs().size() >= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLGizmoAlignment::validate_selection_for_distribute() const
|
||||||
|
{
|
||||||
|
const Selection& selection = get_selection();
|
||||||
|
|
||||||
|
if (selection.is_single_full_object() || selection.is_multiple_full_object()) {
|
||||||
|
std::set<int> object_indices;
|
||||||
|
for (int volume_idx : selection.get_volume_idxs()) {
|
||||||
|
const GLVolume* volume = selection.get_volume(volume_idx);
|
||||||
|
if (volume) {
|
||||||
|
object_indices.insert(volume->object_idx());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return object_indices.size() >= 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return selection.get_volume_idxs().size() >= 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace GUI
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
#ifndef slic3r_GLGizmoAlignment_hpp_
|
||||||
|
#define slic3r_GLGizmoAlignment_hpp_
|
||||||
|
|
||||||
|
#include "libslic3r/Point.hpp"
|
||||||
|
#include "libslic3r/BoundingBox.hpp"
|
||||||
|
#include "slic3r/GUI/Selection.hpp"
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace GUI {
|
||||||
|
|
||||||
|
class GLCanvas3D;
|
||||||
|
|
||||||
|
class GLGizmoAlignment
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class AlignType {
|
||||||
|
NONE = -1,
|
||||||
|
CENTER_X,
|
||||||
|
CENTER_Y,
|
||||||
|
CENTER_Z,
|
||||||
|
Y_MAX,
|
||||||
|
Y_MIN,
|
||||||
|
X_MAX,
|
||||||
|
X_MIN,
|
||||||
|
Z_MAX,
|
||||||
|
Z_MIN,
|
||||||
|
DISTRIBUTE_X,
|
||||||
|
DISTRIBUTE_Y,
|
||||||
|
DISTRIBUTE_Z
|
||||||
|
};
|
||||||
|
struct ObjectInfo {
|
||||||
|
int object_idx;
|
||||||
|
int instance_idx;
|
||||||
|
BoundingBoxf3 bbox;
|
||||||
|
Vec3d center;
|
||||||
|
|
||||||
|
ObjectInfo(int obj_idx, int inst_idx, const BoundingBoxf3& bb)
|
||||||
|
: object_idx(obj_idx), instance_idx(inst_idx), bbox(bb), center(bb.center()) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit GLGizmoAlignment(GLCanvas3D& canvas);
|
||||||
|
~GLGizmoAlignment() = default;
|
||||||
|
|
||||||
|
bool align_objects(AlignType type,bool align_parent = false);
|
||||||
|
bool distribute_objects(AlignType type);
|
||||||
|
|
||||||
|
bool align_to_center(AlignType type,bool align_parent = false);
|
||||||
|
bool align_to_y_max(bool align_parent = false);
|
||||||
|
bool align_to_y_min(bool align_parent = false);
|
||||||
|
bool align_to_x_max(bool align_parent = false);
|
||||||
|
bool align_to_x_min(bool align_parent = false);
|
||||||
|
bool align_to_z_max(bool align_parent = false);
|
||||||
|
bool align_to_z_min(bool align_parent = false);
|
||||||
|
|
||||||
|
bool distribute_x();
|
||||||
|
bool distribute_y();
|
||||||
|
bool distribute_z();
|
||||||
|
|
||||||
|
bool can_align(AlignType type) const;
|
||||||
|
bool can_distribute(AlignType type) const;
|
||||||
|
|
||||||
|
std::vector<ObjectInfo> get_selected_objects_info(BoundingBoxf3 &big_bb) const;
|
||||||
|
double get_current_coord(std::string operation_name, BoundingBoxf3 &bbox);
|
||||||
|
Vec3d generate_displacement(std::string operation_name, double offset);
|
||||||
|
bool is_part_align_parent();
|
||||||
|
void set_parent_box(const BoundingBoxf3 &bb);
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLCanvas3D& m_canvas;
|
||||||
|
|
||||||
|
template<typename GetCoordFunc, typename SetCoordFunc>
|
||||||
|
bool align_objects_generic(GetCoordFunc get_coord, SetCoordFunc set_coord, const std::string &operation_name, bool align_parent = false);
|
||||||
|
|
||||||
|
template<typename GetCoordFunc>
|
||||||
|
bool distribute_objects_generic(GetCoordFunc get_coord, int axis,
|
||||||
|
const std::string& operation_name);
|
||||||
|
|
||||||
|
Selection& get_selection() const;
|
||||||
|
bool take_snapshot(const std::string& name);
|
||||||
|
void apply_transformation(int obj_idx, int inst_idx, const Vec3d& displacement);
|
||||||
|
void finish_operation(const std::string &operation_name, bool force_volume_move = false);
|
||||||
|
|
||||||
|
bool validate_selection_for_align() const;
|
||||||
|
bool validate_selection_for_distribute() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
BoundingBoxf3 m_parent_box;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace GUI
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif // slic3r_GLGizmoAlignment_hpp_
|
||||||
@@ -71,6 +71,7 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <map>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <set>
|
#include <set>
|
||||||
@@ -3917,7 +3918,7 @@ void GLGizmoTextureDisplacement::set_layer_texture(TextureDisplacementLayer &lay
|
|||||||
// the PNG the first time the layer is previewed or baked.
|
// the PNG the first time the layer is previewed or baked.
|
||||||
layer.image_data = tex->image_data;
|
layer.image_data = tex->image_data;
|
||||||
// The user's own textures are listed most recently used first.
|
// The user's own textures are listed most recently used first.
|
||||||
if (entry.is_user)
|
if (entry.source == TextureSource::User)
|
||||||
touch_user_texture(entry.path);
|
touch_user_texture(entry.path);
|
||||||
|
|
||||||
rebuild_preview();
|
rebuild_preview();
|
||||||
@@ -3964,12 +3965,41 @@ void GLGizmoTextureDisplacement::render_texture_library_popup(const ImVec2 &pane
|
|||||||
const float spacing = std::round(m_imgui->scaled(0.4f));
|
const float spacing = std::round(m_imgui->scaled(0.4f));
|
||||||
const float grid_w = 4.f * tile + 3.f * spacing;
|
const float grid_w = 4.f * tile + 3.f * spacing;
|
||||||
const float gap = m_imgui->scaled(0.5f);
|
const float gap = m_imgui->scaled(0.5f);
|
||||||
|
const ImGuiStyle &style = ImGui::GetStyle();
|
||||||
|
|
||||||
|
// A fixed size rather than a fit-to-content one. The width is the four-column grid plus the window
|
||||||
|
// padding and the scrollbar gutter, so the columns always fit and no horizontal scrollbar is needed.
|
||||||
|
// The height is a share of the window, not of the content, so it does not jump as packs load or
|
||||||
|
// sections collapse; the width derives from scaled values, so it tracks the DPI like the rest.
|
||||||
|
const float fixed_w = grid_w + 2.f * style.WindowPadding.x + style.ScrollbarSize;
|
||||||
|
|
||||||
|
// BeginPopup() ORs in ImGuiWindowFlags_AlwaysAutoResize (deps_src/imgui/imgui.cpp:8371) and no flag
|
||||||
|
// turns it back off. A SetNextWindowSize() overrides it for each axis it sets (imgui.cpp:5983-5996),
|
||||||
|
// so an explicit size is the only way to fix the popup's size; the window then scrolls its content.
|
||||||
|
//
|
||||||
|
// About 70% of the window, fixed for a given window, shortened only enough to keep a gap above and
|
||||||
|
// below, and floored at two frame heights so a very short window still shows something usable.
|
||||||
|
const ImGuiViewport *viewport = ImGui::GetMainViewport();
|
||||||
|
const float height = std::max(std::min(0.70f * viewport->Size.y, viewport->Size.y - 2.f * gap),
|
||||||
|
2.f * ImGui::GetFrameHeight());
|
||||||
|
|
||||||
|
// A 70%-tall popup cannot hang off the panel's top: anchored there, the side it grows to holds only
|
||||||
|
// about half the window. It uses the whole column beside the panel instead, filling from the top or
|
||||||
|
// the bottom of the viewport depending on which side of the panel's top has more room.
|
||||||
|
const float popup_top = panel_min.y + m_imgui->scaled(4.f);
|
||||||
|
const float space_below = viewport->Pos.y + viewport->Size.y - popup_top - gap;
|
||||||
|
const float space_above = popup_top - viewport->Pos.y - gap;
|
||||||
|
const bool grow_down = space_below >= space_above;
|
||||||
|
const float popup_y = grow_down ? viewport->Pos.y + gap
|
||||||
|
: viewport->Pos.y + viewport->Size.y - gap - height;
|
||||||
// Beside the panel rather than over it, so the layer being retextured stays in view: to its left when
|
// Beside the panel rather than over it, so the layer being retextured stays in view: to its left when
|
||||||
// there is room (the docked panel sits against the right edge of the canvas), otherwise to its right.
|
// there is room (the docked panel sits against the right edge of the canvas), otherwise to its right.
|
||||||
const float popup_w = grid_w + 2.f * ImGui::GetStyle().WindowPadding.x;
|
const float width = viewport->Size.x > 0.f ? std::min(fixed_w, viewport->Size.x) : fixed_w;
|
||||||
const bool left = panel_min.x - gap - popup_w >= 0.f;
|
const bool left = panel_min.x - gap - width >= 0.f;
|
||||||
ImGui::SetNextWindowPos(ImVec2(left ? panel_min.x - gap : panel_max.x + gap, panel_min.y + m_imgui->scaled(4.f)),
|
const float popup_x = left ? panel_min.x - gap - width : panel_max.x + gap;
|
||||||
ImGuiCond_Appearing, ImVec2(left ? 1.f : 0.f, 0.f));
|
// Cond_Always, not Appearing: a viewport resize while the popup is open must move and resize it too.
|
||||||
|
ImGui::SetNextWindowPos(ImVec2(popup_x, popup_y), ImGuiCond_Always);
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(width, height), ImGuiCond_Always);
|
||||||
if (!ImGui::BeginPopup(popup_id))
|
if (!ImGui::BeginPopup(popup_id))
|
||||||
return;
|
return;
|
||||||
if (layer == nullptr) {
|
if (layer == nullptr) {
|
||||||
@@ -3983,6 +4013,12 @@ void GLGizmoTextureDisplacement::render_texture_library_popup(const ImVec2 &pane
|
|||||||
m_imgui->pop_bold_font();
|
m_imgui->pop_bold_font();
|
||||||
|
|
||||||
const std::vector<TextureLibraryEntry> &library = texture_library();
|
const std::vector<TextureLibraryEntry> &library = texture_library();
|
||||||
|
// A reinstalled pack can drop new bytes at a path already cached on this gizmo, and get_library_texture()
|
||||||
|
// caches a failure as well as a success, so a moved generation makes every cached entry suspect.
|
||||||
|
if (const uint64_t generation = texture_library_generation(); generation != m_library_generation) {
|
||||||
|
m_library_textures.clear();
|
||||||
|
m_library_generation = generation;
|
||||||
|
}
|
||||||
ImDrawList *dl = ImGui::GetWindowDrawList();
|
ImDrawList *dl = ImGui::GetWindowDrawList();
|
||||||
const ImU32 selected_col = ImGui::GetColorU32(ImGuiWrapper::COL_ORCA);
|
const ImU32 selected_col = ImGui::GetColorU32(ImGuiWrapper::COL_ORCA);
|
||||||
std::optional<TextureLibraryEntry> chosen;
|
std::optional<TextureLibraryEntry> chosen;
|
||||||
@@ -4003,16 +4039,15 @@ void GLGizmoTextureDisplacement::render_texture_library_popup(const ImVec2 &pane
|
|||||||
ImGui::Dummy(ImVec2(grid_w, float(rows) * tile + float(rows - 1) * spacing));
|
ImGui::Dummy(ImVec2(grid_w, float(rows) * tile + float(rows - 1) * spacing));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// One group's tiles: shipped textures alphabetically, or the user's own most recently used first (the
|
// One section's tiles, in the order the section holds them (shipped alphabetically, pack textures by
|
||||||
// order texture_library() keeps), those with a remove button on hover. Returns how many it drew.
|
// name, the user's own most recently used first - the order texture_library() produced); a user tile
|
||||||
const auto grid = [&](const ImVec2 &origin, bool user) {
|
// carries a remove button on hover. Returns how many it drew.
|
||||||
|
const auto grid = [&](const ImVec2 &origin, const std::vector<const TextureLibraryEntry *> &entries, bool removable) {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
for (const TextureLibraryEntry &entry : library) {
|
for (const TextureLibraryEntry *entry : entries) {
|
||||||
if (entry.is_user != user)
|
|
||||||
continue;
|
|
||||||
ImGui::SetCursorScreenPos(tile_pos(origin, n++));
|
ImGui::SetCursorScreenPos(tile_pos(origin, n++));
|
||||||
ImGui::PushID(entry.path.c_str());
|
ImGui::PushID(entry->path.c_str());
|
||||||
const LibraryTexture *tex = get_library_texture(entry.path);
|
const LibraryTexture *tex = get_library_texture(entry->path);
|
||||||
GLTexture *thumb = tex != nullptr ? tex->thumbnail.get() : nullptr;
|
GLTexture *thumb = tex != nullptr ? tex->thumbnail.get() : nullptr;
|
||||||
bool clicked = false;
|
bool clicked = false;
|
||||||
if (thumb != nullptr) {
|
if (thumb != nullptr) {
|
||||||
@@ -4030,15 +4065,17 @@ void GLGizmoTextureDisplacement::render_texture_library_popup(const ImVec2 &pane
|
|||||||
} else {
|
} else {
|
||||||
clicked = ImGui::Button("?", ImVec2(tile, tile));
|
clicked = ImGui::Button("?", ImVec2(tile, tile));
|
||||||
}
|
}
|
||||||
if (user)
|
if (removable)
|
||||||
ImGui::SetItemAllowOverlap(); // the remove button drawn over its corner takes its own clicks
|
ImGui::SetItemAllowOverlap(); // the remove button drawn over its corner takes its own clicks
|
||||||
const ImVec2 a = ImGui::GetItemRectMin(), b = ImGui::GetItemRectMax();
|
const ImVec2 a = ImGui::GetItemRectMin(), b = ImGui::GetItemRectMax();
|
||||||
if (entry.path == layer->path)
|
if (entry->path == layer->path)
|
||||||
dl->AddRect(ImVec2(a.x - 1.f, a.y - 1.f), ImVec2(b.x + 1.f, b.y + 1.f), selected_col, 0.f, 0, 2.f);
|
dl->AddRect(ImVec2(a.x - 1.f, a.y - 1.f), ImVec2(b.x + 1.f, b.y + 1.f), selected_col, 0.f, 0, 2.f);
|
||||||
if (ImGui::IsItemHovered())
|
if (ImGui::IsItemHovered())
|
||||||
m_imgui->tooltip(entry.name, m_imgui->scaled(18.f));
|
m_imgui->tooltip(entry->name, m_imgui->scaled(18.f));
|
||||||
|
|
||||||
if (user && ImGui::IsMouseHoveringRect(a, b)) {
|
// Clipped by the window's own clip rect, so a tile scrolled under the heading or the scrollbar
|
||||||
|
// does not offer its remove button. IsMouseHoveringRect() clips by default.
|
||||||
|
if (removable && ImGui::IsMouseHoveringRect(a, b)) {
|
||||||
const float r = std::round(tile * 0.17f);
|
const float r = std::round(tile * 0.17f);
|
||||||
const ImVec2 c(b.x - r - 2.f, a.y + r + 2.f);
|
const ImVec2 c(b.x - r - 2.f, a.y + r + 2.f);
|
||||||
ImGui::SetCursorScreenPos(ImVec2(c.x - r, c.y - r));
|
ImGui::SetCursorScreenPos(ImVec2(c.x - r, c.y - r));
|
||||||
@@ -4051,41 +4088,105 @@ void GLGizmoTextureDisplacement::render_texture_library_popup(const ImVec2 &pane
|
|||||||
if (hot)
|
if (hot)
|
||||||
m_imgui->tooltip(_u8L("Remove from My textures"), m_imgui->scaled(18.f));
|
m_imgui->tooltip(_u8L("Remove from My textures"), m_imgui->scaled(18.f));
|
||||||
if (remove_clicked)
|
if (remove_clicked)
|
||||||
to_remove = entry;
|
to_remove = *entry;
|
||||||
}
|
}
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
if (clicked)
|
if (clicked)
|
||||||
chosen = entry;
|
chosen = *entry;
|
||||||
}
|
}
|
||||||
return n;
|
return n;
|
||||||
};
|
};
|
||||||
|
|
||||||
ImGui::TextDisabled("%s", _u8L("Built-in").c_str());
|
// Every section collapses; the state is remembered for the app run. Looked up by section name - the
|
||||||
{
|
// fixed Built-in / My textures keys, or a pack's display name. A capability name is unique per type
|
||||||
const ImVec2 origin = ImGui::GetCursorScreenPos();
|
// across loaded packages (PluginManager::check_registry_locked, PluginManager.cpp:211-225), and it is
|
||||||
const int count = grid(origin, false);
|
// stable across reinstalls, unlike the plugin_key, which is the wheel filename stem and so carries the
|
||||||
end_grid(origin, count);
|
// version: keying on that would silently reset the user's collapse choice on every pack upgrade. A
|
||||||
if (count == 0)
|
// function-local static lives exactly as long as the app and never touches app_config: remembered
|
||||||
ImGui::TextDisabled("%s", _u8L("No textures found.").c_str());
|
// while the popup closes and reopens, reset on restart.
|
||||||
}
|
static std::map<std::string, bool> s_section_collapsed; // true = collapsed; absent = expanded
|
||||||
ImGui::TextDisabled("%s", _u8L("My textures").c_str());
|
|
||||||
{
|
|
||||||
const ImVec2 origin = ImGui::GetCursorScreenPos();
|
|
||||||
const int count = grid(origin, true);
|
|
||||||
ImGui::SetCursorScreenPos(tile_pos(origin, count));
|
|
||||||
if (ImGui::Button("+##import_texture", ImVec2(tile, tile)))
|
|
||||||
import_clicked = true;
|
|
||||||
if (ImGui::IsItemHovered())
|
|
||||||
m_imgui->tooltip(_u8L("Import your own image as a height map. It is converted to the format the slicer "
|
|
||||||
"bakes from and saved to your personal texture folder, kept separate from the "
|
|
||||||
"textures shipped with OrcaSlicer."),
|
|
||||||
m_imgui->scaled(20.f));
|
|
||||||
end_grid(origin, count + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + grid_w);
|
// A section heading with its collapse control at the right edge of the grid: the triangle points
|
||||||
ImGui::TextDisabled("%s", _u8L("PNG, JPG or BMP - converted to a height map on import. Hover one of yours to remove it.").c_str());
|
// down while the section is open and right while it is collapsed, and toggles it when clicked.
|
||||||
ImGui::PopTextWrapPos();
|
// Returns true when the section is open, so the caller knows whether to draw its grid.
|
||||||
|
const auto section_header = [&](const std::string &key, const std::string &title) {
|
||||||
|
bool &collapsed = s_section_collapsed[key]; // default-inserts false -> expanded
|
||||||
|
const ImVec2 row = ImGui::GetCursorScreenPos();
|
||||||
|
const float row_h = ImGui::GetFrameHeight();
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(row.x, row.y + (row_h - ImGui::GetTextLineHeight()) * 0.5f));
|
||||||
|
ImGui::TextDisabled("%s", title.c_str());
|
||||||
|
const bool clipped = ImGui::CalcTextSize(title.c_str()).x > grid_w - row_h - spacing;
|
||||||
|
if (clipped && ImGui::IsItemHovered())
|
||||||
|
m_imgui->tooltip(title, m_imgui->scaled(18.f)); // a long plugin-supplied name, cut by the fixed width
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(row.x + grid_w - row_h, row.y));
|
||||||
|
ImGui::PushID(key.c_str());
|
||||||
|
if (ImGui::ArrowButton("##collapse", collapsed ? ImGuiDir_Right : ImGuiDir_Down))
|
||||||
|
collapsed = !collapsed;
|
||||||
|
ImGui::PopID();
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(row.x, row.y + row_h + ImGui::GetStyle().ItemSpacing.y));
|
||||||
|
return !collapsed;
|
||||||
|
};
|
||||||
|
|
||||||
|
// One section per source: Built-in first, then one per pack, then My textures last (so the import tile
|
||||||
|
// and the help line keep describing the group they sit in). Pack entries are grouped by pack_name,
|
||||||
|
// which is the capability's get_name(): unique per capability type across loaded packages
|
||||||
|
// (PluginManager::check_registry_locked, PluginManager.cpp:211-225), so it identifies a pack both for
|
||||||
|
// grouping and for the remembered collapse state, and stands in for the section title. A section is
|
||||||
|
// identified by `builtin:`, `user:` or `pack:<name>`, so a pack named "builtin" cannot collide with
|
||||||
|
// the fixed Built-in section.
|
||||||
|
struct Section
|
||||||
|
{
|
||||||
|
std::string key;
|
||||||
|
std::string title;
|
||||||
|
TextureSource source;
|
||||||
|
std::vector<const TextureLibraryEntry *> entries;
|
||||||
|
};
|
||||||
|
std::vector<Section> sections;
|
||||||
|
std::map<std::string, size_t> pack_section; // pack_name -> index in `sections`
|
||||||
|
sections.push_back({ "builtin:", _u8L("Built-in"), TextureSource::Shipped, {} });
|
||||||
|
for (const TextureLibraryEntry &entry : library) {
|
||||||
|
if (entry.source == TextureSource::Shipped) {
|
||||||
|
sections.front().entries.push_back(&entry); // the section exists even when the library is empty
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (entry.source == TextureSource::User)
|
||||||
|
continue; // added below, so My textures stays last
|
||||||
|
const auto [it, inserted] = pack_section.emplace(entry.pack_name, sections.size());
|
||||||
|
if (inserted)
|
||||||
|
sections.push_back({ "pack:" + entry.pack_name, entry.pack_name, TextureSource::Plugin, {} });
|
||||||
|
sections[it->second].entries.push_back(&entry);
|
||||||
|
}
|
||||||
|
sections.push_back({ "user:", _u8L("My textures"), TextureSource::User, {} });
|
||||||
|
for (const TextureLibraryEntry &entry : library)
|
||||||
|
if (entry.source == TextureSource::User)
|
||||||
|
sections.back().entries.push_back(&entry);
|
||||||
|
|
||||||
|
for (const Section §ion : sections) {
|
||||||
|
if (!section_header(section.key, section.title))
|
||||||
|
continue;
|
||||||
|
const ImVec2 origin = ImGui::GetCursorScreenPos();
|
||||||
|
const int count = grid(origin, section.entries, section.source == TextureSource::User);
|
||||||
|
if (section.source == TextureSource::User) {
|
||||||
|
ImGui::SetCursorScreenPos(tile_pos(origin, count));
|
||||||
|
if (ImGui::Button("+##import_texture", ImVec2(tile, tile)))
|
||||||
|
import_clicked = true;
|
||||||
|
if (ImGui::IsItemHovered())
|
||||||
|
m_imgui->tooltip(_u8L("Import your own image as a height map. It is converted to the format the slicer "
|
||||||
|
"bakes from and saved to your personal texture folder, kept separate from the "
|
||||||
|
"textures shipped with OrcaSlicer."),
|
||||||
|
m_imgui->scaled(20.f));
|
||||||
|
end_grid(origin, count + 1);
|
||||||
|
// Belongs to this section: it describes the import tile and the remove-on-hover tiles, both of
|
||||||
|
// which live here, so it is hidden with them rather than left describing controls off-screen.
|
||||||
|
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + grid_w);
|
||||||
|
ImGui::TextDisabled("%s", _u8L("PNG, JPG or BMP - converted to a height map on import. Hover one of yours to remove it.").c_str());
|
||||||
|
ImGui::PopTextWrapPos();
|
||||||
|
} else {
|
||||||
|
end_grid(origin, count);
|
||||||
|
if (section.source == TextureSource::Shipped && count == 0)
|
||||||
|
ImGui::TextDisabled("%s", _u8L("No textures found.").c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (chosen || import_clicked)
|
if (chosen || import_clicked)
|
||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user