Compare commits

..
Author SHA1 Message Date
SoftFever 12b6d6b9ba Merge branch 'main' into hanif/preview-drag-detail 2026-09-29 16:14:34 +08:00
Hanif Koh c6d89d6978 Hide Infill in the Shell Drag Mode Only Where the Profile Covers It
A profile with no top or bottom shell layers, or no walls, leaves the infill on the surface,
so hiding it changed the picture. The viewer now reads the print's default region settings
when it loads a print and leaves the infill in place for such a print; per-object overrides
are not consulted.
2026-09-28 17:10:54 +08:00
Hanif Koh abecef53b0 Keep Only the Solid Model and Shell Modes While Dragging
The skip-layers and outer-walls modes drew one layer in every N, and every visual
complaint on the review came from the gaps that leaves at close range. The shell mode
draws every layer and looks the same from outside, and the solid model is the one mode
whose cost does not depend on the toolpaths, so the two of them cover what the stride
modes were for. The layer stride goes with them; a saved preference naming a removed
mode falls back to off.
2026-09-28 17:10:54 +08:00
Hanif Koh 44d5a22a32 Add a Shell-Only Drag Mode That Keeps What Can Be Seen From Outside
A third mode of the drag preference: the reduced set keeps every role but the sparse
infill, internal solid infill, internal bridge infill and gap fill, which lie under the
skins or between the walls, on every layer. Walls, top and bottom surfaces, bridges,
supports and the prime tower are drawn whole, so from outside the print looks as it does
at rest, and the two end layers of the visible range stay whole as in every mode.

The roles come from the slicer, so the classification is exact wherever the slicer's is.
What a role cannot tell apart is the inside of the prime tower or of a support from its
outside; both are kept whole. A profile with no top or bottom shell layers exposes infill,
which this mode hides.
2026-09-28 17:10:54 +08:00
Hanif Koh dec146f1e6 Let the User Choose What the Preview Draws While Dragging
The solid model is one answer to a preview that cannot keep up with a drag; a
reviewer asked for the two that keep the drag view made of toolpaths. The
checkbox is now a combobox, "Simplify preview while dragging", with Off, Solid
model, Skip layers and Outer walls, and a spin, "Draw one layer in every N",
for the toolpath modes. Off is the default, and with it nothing is built.

Skip layers keeps one layer in every N. Outer walls keeps the outer and overhang
perimeters of those layers, so the prime tower and supports, whose segments have
other roles, drop out of it. Both keep the bottom and top layers of the visible
range whole, as the solid model does, so the faces the range cuts open stay what
was printed there.

libvgcode takes the choice as EReducedDetailMode and a stride; the sets are
rebuilt when either changes, and drawing from the reduced one is still a buffer
binding. A mode change reaches the loaded preview at once, as the checkbox did.
2026-09-28 16:20:11 +08:00
Hanif Koh a9fec0ffc4 Release the Reduced Set When Unused and Share the Drag Check With the Scene Cache
The reduced index buffers were only re-uploaded while the preference was on, so
the last set stayed allocated until the next load once it was switched off, and
their size was missing from get_used_gpu_memory(). They are now uploaded on every
rebuild, empty when nothing was built, and counted.

The scene cache and the solid model both asked whether the user was dragging,
with different lists: the cache knew about gizmos and the rectangle selection,
the solid model about the navigator and the sliders. One is_user_interacting()
now answers both.

The tooltip says that negative volumes are not cut out of the solid model, since
load_shells() drops every non-model-part volume, and the preferences handler
keeps the dimming comment with the branch it documents.
2026-09-28 16:20:11 +08:00
Hanif Koh 9ab6bbebf0 Offer the Sliced Objects as a Solid Model While Dragging
The G-code preview draws every toolpath segment as an instanced box, and its frame cost is
linear in the number of segments drawn. On a plate of large objects that is enough that
dragging the camera or a preview slider cannot keep up, and no amount of per-segment work
changes that; only drawing fewer segments does.

The objects themselves are cheaper: a mesh costs its triangles once, however many layers it
has, and the preview already loads the objects as shells for its translucent ghost. A new
preference, "Only render solid model when dragging" (off by default), draws those shells
opaque in their filament colours instead of the toolpaths while the user drags.

The visible layer range still holds. The shells are cut at the range's top and bottom through
the gouraud shader's z range, and libvgcode keeps a second index buffer holding just the
range's bottom and top layers, filled in the same walk as the full one, which is drawn
afterwards so that it caps the cut with what was really printed there. Switching between the
two sets is a buffer binding, never a rebuild. The prime tower is added to the shells from its
sliced mesh while the preference is on, positioned as the print placed it; it keeps its opaque
colour and so stays out of the translucent ghost. Supports have no mesh and are not shown.

Dragging is the camera, the navigator or either slider being held; a slider reports it from
ImGui's active id, since its dirty flag is raised and consumed inside one frame. A wheel step
holds the solid model for a 150 ms settle time, with the frame that restores the toolpaths
scheduled for when it runs out. The switch is decided at the top of the frame, before the
cached scene is consulted, and a frame that switches redraws the scene. A drag cut short by
focus or capture loss is ended explicitly, and a button release wakes the idle loop, since on
some platforms no idle event follows it until the next input.

With the preference off, nothing is built and the preview is unchanged.
2026-09-28 16:20:11 +08:00
215 changed files with 3120 additions and 9003 deletions
@@ -189,7 +189,8 @@ the presets to a base is in [shared-bases.md](shared-bases.md#variant-arrays-on-
2. Every key in `filament_options_with_variant` ([the full list](#the-four-key-sets)) that reaches the
preset, whether written, included or inherited, is resized to its variant count: write it at exactly
that width, or leave it out. Keys outside the set
(`filament_type`, plate temperatures, `fan_max_speed`, `slow_down_min_speed`, …) are never addressed by variant index; the preset contributes their first value however wide a
(`filament_type`, plate temperatures, `fan_max_speed`, `slow_down_min_speed`, `pressure_advance`,
…) are never addressed by variant index; the preset contributes their first value however wide a
file writes them.
3. Cover every variant the material is meant to print on across its `compatible_printers`. Leave out
a variant deliberately when the material should not be tuned for it (e.g. a TPU High Flow variant
@@ -454,7 +455,7 @@ no set: the variant-length resize leaves it alone, and only the pair rebuild of
last for contrast; it is not a variant set.
`check` and `fix-variant` read the four sets from `PrintConfig.cpp` on every run, so they follow the
engine. The lists below are from the 2026-09-29 checkout; regenerate them from the repository root
engine. The lists below are from the 2026-09-25 checkout; regenerate them from the repository root
before relying on them (the recipe strips comments, since an initializer can carry a commented-out entry):
```bash
@@ -477,7 +478,7 @@ EOF
**`print_options_with_variant`**, process, stride 1 (45): `bridge_acceleration`, `bridge_speed`, `default_acceleration`, `default_jerk`, `default_junction_deviation`, `enable_overhang_speed`, `gap_infill_speed`, `infill_jerk`, `initial_layer_acceleration`, `initial_layer_infill_speed`, `initial_layer_jerk`, `initial_layer_speed`, `initial_layer_travel_acceleration`, `initial_layer_travel_jerk`, `initial_layer_travel_speed`, `inner_wall_acceleration`, `inner_wall_jerk`, `inner_wall_speed`, `internal_bridge_speed`, `internal_solid_infill_acceleration`, `internal_solid_infill_speed`, `outer_wall_acceleration`, `outer_wall_jerk`, `outer_wall_speed`, `overhang_1_4_speed`, `overhang_2_4_speed`, `overhang_3_4_speed`, `overhang_4_4_speed`, `print_extruder_id`, `print_extruder_variant`, `slowdown_for_curled_perimeters`, `small_perimeter_speed`, `small_perimeter_threshold`, `sparse_infill_acceleration`, `sparse_infill_speed`, `support_interface_speed`, `support_speed`, `top_solid_infill_flow_ratio`, `top_surface_acceleration`, `top_surface_jerk`, `top_surface_speed`, `travel_acceleration`, `travel_jerk`, `travel_speed`, `travel_speed_z`
**`filament_options_with_variant`**, filament, stride 1 (54): `activate_air_filtration`, `activate_air_filtration_during_print`, `activate_air_filtration_on_completion`, `adaptive_pressure_advance`, `adaptive_pressure_advance_bridges`, `adaptive_pressure_advance_model`, `adaptive_pressure_advance_overhangs`, `complete_print_exhaust_fan_speed`, `during_print_exhaust_fan_speed`, `enable_pressure_advance`, `filament_adaptive_volumetric_speed`, `filament_cooling_before_tower`, `filament_deretraction_speed`, `filament_extruder_variant`, `filament_flow_ratio`, `filament_flush_temp`, `filament_flush_volumetric_speed`, `filament_ironing_flow`, `filament_ironing_inset`, `filament_ironing_spacing`, `filament_ironing_speed`, `filament_long_retractions_when_cut`, `filament_max_volumetric_speed`, `filament_pre_cooling_temperature`, `filament_pre_cooling_temperature_nc`, `filament_preheat_temperature_delta`, `filament_ramming_travel_time`, `filament_ramming_travel_time_nc`, `filament_ramming_volumetric_speed`, `filament_ramming_volumetric_speed_nc`, `filament_retract_after_wipe`, `filament_retract_before_wipe`, `filament_retract_length_nc`, `filament_retract_length_toolchange`, `filament_retract_lift_above`, `filament_retract_lift_below`, `filament_retract_lift_enforce`, `filament_retract_restart_extra`, `filament_retract_restart_extra_toolchange`, `filament_retract_when_changing_layer`, `filament_retraction_distances_when_cut`, `filament_retraction_length`, `filament_retraction_minimum_travel`, `filament_retraction_speed`, `filament_wipe`, `filament_wipe_distance`, `filament_z_hop`, `filament_z_hop_types`, `long_retractions_when_ec`, `nozzle_temperature`, `nozzle_temperature_initial_layer`, `pressure_advance`, `retraction_distances_when_ec`, `volumetric_speed_coefficients`
**`filament_options_with_variant`**, filament, stride 1 (48): `activate_air_filtration`, `activate_air_filtration_during_print`, `activate_air_filtration_on_completion`, `complete_print_exhaust_fan_speed`, `during_print_exhaust_fan_speed`, `filament_adaptive_volumetric_speed`, `filament_cooling_before_tower`, `filament_deretraction_speed`, `filament_extruder_variant`, `filament_flow_ratio`, `filament_flush_temp`, `filament_flush_volumetric_speed`, `filament_ironing_flow`, `filament_ironing_inset`, `filament_ironing_spacing`, `filament_ironing_speed`, `filament_long_retractions_when_cut`, `filament_max_volumetric_speed`, `filament_pre_cooling_temperature`, `filament_pre_cooling_temperature_nc`, `filament_preheat_temperature_delta`, `filament_ramming_travel_time`, `filament_ramming_travel_time_nc`, `filament_ramming_volumetric_speed`, `filament_ramming_volumetric_speed_nc`, `filament_retract_after_wipe`, `filament_retract_before_wipe`, `filament_retract_length_nc`, `filament_retract_length_toolchange`, `filament_retract_lift_above`, `filament_retract_lift_below`, `filament_retract_lift_enforce`, `filament_retract_restart_extra`, `filament_retract_restart_extra_toolchange`, `filament_retract_when_changing_layer`, `filament_retraction_distances_when_cut`, `filament_retraction_length`, `filament_retraction_minimum_travel`, `filament_retraction_speed`, `filament_wipe`, `filament_wipe_distance`, `filament_z_hop`, `filament_z_hop_types`, `long_retractions_when_ec`, `nozzle_temperature`, `nozzle_temperature_initial_layer`, `retraction_distances_when_ec`, `volumetric_speed_coefficients`
**`printer_extruder_options`**, machine, one value per extruder, not a variant set (8):
`default_nozzle_volume_type`, `extruder_max_nozzle_count`, `extruder_printable_area`,
@@ -244,10 +244,10 @@ preset for a 0.2 nozzle, explicitly revisit flow limits; do not infer a pressure
required direction of change, from diameter alone.
On a printer with extruder variants, a filament tunes these per variant too:
`filament_max_volumetric_speed`, `filament_flow_ratio`, `nozzle_temperature`, pressure advance and the
retraction overrides carry one value per variant of `filament_extruder_variant` (Standard, High Flow, …). The
`filament_max_volumetric_speed`, `filament_flow_ratio`, `nozzle_temperature` and the retraction
overrides carry one value per variant of `filament_extruder_variant` (Standard, High Flow, …). The
exact key set is [`filament_options_with_variant`](extruder-variants.md#the-four-key-sets);
`slow_down_min_speed` and `fan_max_speed` are not in it. Keep every such array at exactly that width, even where the
`slow_down_min_speed` and `pressure_advance` are not in it. Keep every such array at exactly that width, even where the
setting does not differ per variant, and measure the High Flow variant rather than copying Standard
([extruder-variants.md](extruder-variants.md#filament)).
+1 -1
View File
@@ -146,7 +146,7 @@ jobs:
"repos/${{ github.repository }}/actions/workflows/post_merge_profiles.yml/runs" \
-f branch="$branch" -f event=workflow_dispatch -f per_page=100)"
run_id="$(jq -r --arg marker "[OFL cron $dispatch_id]" \
'[.workflow_runs[] | select((.display_title // "") | contains($marker))]
'[.workflow_runs[] | select((.display_title // "") | contains($marker))] \
| sort_by(.created_at) | last | .id // empty' <<< "$runs_json")"
[ -n "$run_id" ] && break
sleep 5
+1 -42
View File
@@ -834,37 +834,6 @@ find_package(OpenSSL REQUIRED)
find_package(CURL REQUIRED)
find_package(Freetype REQUIRED)
if (SLIC3R_GUI)
# LibDataChannel's installed export references its bundled dependencies,
# but does not install their CMake targets. Recreate those targets from
# the same dependency prefix before loading the LibDataChannel config.
if (NOT TARGET Usrsctp::usrsctp)
find_library(_ORCA_USRSCTP_LIBRARY NAMES usrsctp
PATHS "${CMAKE_PREFIX_PATH}/lib" NO_DEFAULT_PATH)
if (_ORCA_USRSCTP_LIBRARY)
add_library(Usrsctp::usrsctp UNKNOWN IMPORTED GLOBAL)
set_target_properties(Usrsctp::usrsctp PROPERTIES
IMPORTED_LOCATION "${_ORCA_USRSCTP_LIBRARY}"
IMPORTED_LINK_INTERFACE_LANGUAGES C
INTERFACE_LINK_LIBRARIES "Threads::Threads")
endif()
endif()
if (NOT TARGET LibJuice::LibJuice)
find_library(_ORCA_LIBJUICE_LIBRARY NAMES juice
PATHS "${CMAKE_PREFIX_PATH}/lib" NO_DEFAULT_PATH)
if (_ORCA_LIBJUICE_LIBRARY)
add_library(LibJuice::LibJuice UNKNOWN IMPORTED GLOBAL)
set_target_properties(LibJuice::LibJuice PROPERTIES
IMPORTED_LOCATION "${_ORCA_LIBJUICE_LIBRARY}"
IMPORTED_LINK_INTERFACE_LANGUAGES C
INTERFACE_LINK_LIBRARIES "Threads::Threads")
endif()
endif()
find_package(LibDataChannel CONFIG REQUIRED)
endif()
add_library(libcurl INTERFACE)
target_link_libraries(libcurl INTERFACE CURL::libcurl)
@@ -1145,7 +1114,6 @@ function(orcaslicer_copy_dlls target config postfix output_dlls)
endif ()
file(COPY ${_occt_dlls}
${CMAKE_PREFIX_PATH}/bin/freetype.dll
${CMAKE_PREFIX_PATH}/bin/avformat-61.dll
${CMAKE_PREFIX_PATH}/bin/avcodec-61.dll
${CMAKE_PREFIX_PATH}/bin/swresample-5.dll
${CMAKE_PREFIX_PATH}/bin/swscale-8.dll
@@ -1158,7 +1126,6 @@ function(orcaslicer_copy_dlls target config postfix output_dlls)
${_out_dir}/WebView2Loader.dll
${_out_dir}/freetype.dll
${_out_dir}/avformat-61.dll
${_out_dir}/avcodec-61.dll
${_out_dir}/swresample-5.dll
${_out_dir}/swscale-8.dll
@@ -1182,10 +1149,7 @@ function(orcaslicer_copy_sos target config postfix output_sos)
set(_out_dir "${CMAKE_CURRENT_BINARY_DIR}")
endif ()
file(COPY ${CMAKE_PREFIX_PATH}/lib/libavformat.so
${CMAKE_PREFIX_PATH}/lib/libavformat.so.61
${CMAKE_PREFIX_PATH}/lib/libavformat.so.61.1.100
${CMAKE_PREFIX_PATH}/lib/libavcodec.so
file(COPY ${CMAKE_PREFIX_PATH}/lib/libavcodec.so
${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61
${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61.3.100
${CMAKE_PREFIX_PATH}/lib/libavutil.so
@@ -1200,9 +1164,6 @@ function(orcaslicer_copy_sos target config postfix output_sos)
DESTINATION ${_out_dir})
set(${output_sos}
${_out_dir}/libavformat.so
${_out_dir}/libavformat.so.61
${_out_dir}/libavformat.so.61.1.100
${_out_dir}/libavcodec.so
${_out_dir}/libavcodec.so.61
${_out_dir}/libavcodec.so.61.3.100
@@ -1332,8 +1293,6 @@ endif ()
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(LIBRARY_FILES
${LIBDIR_BIN}/libavformat.so.61
${LIBDIR_BIN}/libavformat.so.61.1.100
${LIBDIR_BIN}/libavcodec.so.61
${LIBDIR_BIN}/libavcodec.so.61.3.100
${LIBDIR_BIN}/libavutil.so.59
+11 -23
View File
@@ -164,7 +164,7 @@ if (NOT _is_multi AND NOT CMAKE_BUILD_TYPE)
endif ()
function(orcaslicer_add_cmake_project projectname)
cmake_parse_arguments(P_ARGS "FORWARD_CONFIG" "INSTALL_DIR;BUILD_COMMAND;INSTALL_COMMAND;SOURCE_DIR" "CMAKE_ARGS" ${ARGN})
cmake_parse_arguments(P_ARGS "FORWARD_CONFIG" "INSTALL_DIR;BUILD_COMMAND;INSTALL_COMMAND" "CMAKE_ARGS" ${ARGN})
# MSVC is true for clang-cl as well, so the sub-build toolchain has to key on the
# generator. A non-Visual-Studio superbuild passes its own generator down, and with
@@ -210,18 +210,12 @@ function(orcaslicer_add_cmake_project projectname)
set(_build_j "-j${NPROC}")
endif ()
set(_source_dir_arg "")
if (P_ARGS_SOURCE_DIR)
set(_source_dir_arg SOURCE_DIR ${P_ARGS_SOURCE_DIR})
endif ()
if (NOT IS_CROSS_COMPILE OR NOT APPLE)
ExternalProject_Add(
dep_${projectname}
EXCLUDE_FROM_ALL ON
INSTALL_DIR ${DESTDIR}
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/${projectname}
${_source_dir_arg}
${_gen}
CMAKE_ARGS
-DCMAKE_POLICY_VERSION_MINIMUM=3.5
@@ -255,14 +249,12 @@ if (NOT IS_CROSS_COMPILE OR NOT APPLE)
# note for future devs: shared libs may actually create a size reduction
# but orcaslicer_deps tends to get really funny regarding linking after that (notably boost)
# so, as much as I would like to use that, it's not happening
if (NOT P_ARGS_SOURCE_DIR)
ExternalProject_Add_Step(dep_${projectname} free_download_space
DEPENDEES download # do after download
COMMENT "Freeing Space: Removing source archive"
WORKING_DIRECTORY ${DEP_DOWNLOAD_DIR}
COMMAND ${CMAKE_COMMAND} -E rm -rf ${projectname}
)
endif ()
ExternalProject_Add_Step(dep_${projectname} free_download_space
DEPENDEES download # do after download
COMMENT "Freeing Space: Removing source archive"
WORKING_DIRECTORY ${DEP_DOWNLOAD_DIR}
COMMAND ${CMAKE_COMMAND} -E rm -r ${projectname}
)
ExternalProject_Add_Step(dep_${projectname} free_build_space
DEPENDEES install # do after install
COMMENT "Freeing Space: Removing source and build files"
@@ -276,7 +268,6 @@ else()
EXCLUDE_FROM_ALL ON
INSTALL_DIR ${DESTDIR}
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/${projectname}
${_source_dir_arg}
${_gen}
CMAKE_ARGS
-DCMAKE_POLICY_VERSION_MINIMUM=3.5
@@ -405,6 +396,10 @@ include(libnoise/libnoise.cmake)
include(Draco/Draco.cmake)
include(FFMPEG/FFMPEG.cmake)
include(Assimp/Assimp.cmake)
# I *think* 1.1 is used for *just* md5 hashing?
# 3.1 has everything in the right place, but the md5 funcs used are deprecated
# a grep across the repo shows it is used for other things
@@ -415,12 +410,6 @@ if(NOT OPENSSL_FOUND)
set(OPENSSL_PKG dep_OpenSSL)
endif()
include(FFMPEG/FFMPEG.cmake)
include(Assimp/Assimp.cmake)
include(DataChannel/DataChannel.cmake)
set(DATACHANNEL_PKG dep_DataChannel)
# we don't want to load a "wrong" openssl when loading curl
# so, just don't even bother
# ...i think this is how it works? change if wrong
@@ -494,7 +483,6 @@ set(_dep_list
dep_wxInspector
dep_FFMPEG
dep_Assimp
${DATACHANNEL_PKG}
)
if (MSVC)
-37
View File
@@ -1,37 +0,0 @@
# libdatachannel is the native ICE/DTLS/SCTP implementation used by the
# GUI WebRTC camera controller. Keep the source revision fixed: the signaling
# protocol is evolving independently of this transport dependency.
#
# It vendors plog, usrsctp and libjuice as git submodules, which a plain
# GitHub tag tarball does not include. The flatpak sandbox has no network
# access during the build, so there the manifest itself clones the repo
# (submodules and all) into the dependency download directory before the
# sandbox closes. ExternalProject_Add is pointed at that existing checkout
# instead of being given its own network-dependent download method.
if (FLATPAK)
set(_datachannel_source
SOURCE_DIR ${DEP_DOWNLOAD_DIR}/DataChannel
)
else()
set(_datachannel_source
GIT_REPOSITORY https://github.com/paullouisageneau/libdatachannel.git
GIT_TAG v0.24.5
GIT_SHALLOW ON
GIT_SUBMODULES_RECURSE ON
)
endif()
orcaslicer_add_cmake_project(DataChannel
DEPENDS ${OPENSSL_PKG}
CMAKE_ARGS
-DNO_EXAMPLES=ON
-DNO_TESTS=ON
-DNO_WEBSOCKET=ON
-DNO_MEDIA=ON
-DUSE_NICE=OFF
-DUSE_SYSTEM_JUICE=OFF
-DUSE_SYSTEM_USRSCTP=OFF
-DOPENSSL_ROOT_DIR:PATH=${DESTDIR}
-DOPENSSL_USE_STATIC_LIBS=ON
${_datachannel_source}
)
+7 -23
View File
@@ -1,26 +1,14 @@
set(_conf_cmd ./configure)
set(_ffmpeg_depends)
set(_ffmpeg_configure_command ${_conf_cmd})
if (TARGET dep_OpenSSL)
set(_ffmpeg_depends DEPENDS dep_OpenSSL)
set(_ffmpeg_configure_command
${CMAKE_COMMAND} -E env
"PKG_CONFIG_PATH=${DESTDIR}/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}"
${_conf_cmd}
)
endif()
if (MSVC)
set(_source_dir "${CMAKE_BINARY_DIR}/dep_FFMPEG-prefix/src/dep_FFMPEG")
set(PREBUILD_URL_arm64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-09-18-16-50/ffmpeg-n7.0.3-33-g887d4b4919-winarm64-orca-shared-7.0.zip")
set(PREBUILD_HASH_arm64 "da480cbb39680056de824c57ec4dc3bd577b479ebbc310ff1f9dc55cf014b4c1")
set(PREBUILD_URL_x64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-09-18-16-50/ffmpeg-n7.0.3-33-g887d4b4919-win64-orca-shared-7.0.zip")
set(PREBUILD_HASH_x64 "85da19daf198f5548259d8aabb349db84997a3f6e6886d8d7764114add9c6dae")
set(PREBUILD_URL_arm64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-07-17-14-28/ffmpeg-n7.0.3-31-g9b6ffd74b5-winarm64-orca-shared-7.0.zip")
set(PREBUILD_HASH_arm64 "12f4140279f2f8469885e1b5b2e8be9d788882914c21523cacd56989f3548054")
set(PREBUILD_URL_x64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-07-17-14-28/ffmpeg-n7.0.3-31-g9b6ffd74b5-win64-orca-shared-7.0.zip")
set(PREBUILD_HASH_x64 "e65916020ddb9ef84b2666dfbcbfc9b1d67f69d15b4a66db53754637bf2d498c")
ExternalProject_Add(dep_FFMPEG
${_ffmpeg_depends}
URL ${PREBUILD_URL_${DEPS_ARCH}}
URL_HASH SHA256=${PREBUILD_HASH_${DEPS_ARCH}}
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/FFMPEG
@@ -33,8 +21,6 @@ if (MSVC)
)
else ()
set(_openssl_cmd --enable-openssl)
if (APPLE)
set(_minos_cmd
"--extra-cflags=-mmacosx-version-min=${DEP_OSX_TARGET}"
@@ -66,11 +52,10 @@ else ()
endif()
ExternalProject_Add(dep_FFMPEG
${_ffmpeg_depends}
URL https://github.com/FFmpeg/FFmpeg/archive/refs/tags/n7.0.3.tar.gz
URL_HASH SHA256=DEEDCABE339165214A3637DF4C86A507AEF0D793CF8774FF68735F4737E8DDBC
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/FFMPEG
CONFIGURE_COMMAND ${_ffmpeg_configure_command}
CONFIGURE_COMMAND ${_conf_cmd}
${_cross_cmd}
${_pic_cmd}
${_arch_cmd}
@@ -78,21 +63,20 @@ else ()
"--prefix=${DESTDIR}"
${_link_cmd}
${_minos_cmd}
${_openssl_cmd}
--disable-doc
--enable-small
--disable-outdevs
--disable-filters
--enable-filter=*null*,afade,*fifo,*format,*resample,aeval,allrgb,allyuv,atempo,pan,*bars,color,*key,crop,draw*,eq*,framerate,*_qsv,*_vaapi,*v4l2*,hw*,scale,volume,test*
--disable-protocols
--enable-protocol=file,fd,pipe,http,https,rtp,tcp,udp
--enable-protocol=file,fd,pipe,rtp,udp
--disable-muxers
--enable-muxer=rtp
--disable-encoders
--disable-decoders
--enable-decoder=*aac*,h264*,mp3*,mjpeg,rv*
--disable-demuxers
--enable-demuxer=h264,mp3,mov,mpjpeg,rtsp,sdp
--enable-demuxer=h264,mp3,mov
--disable-zlib
--disable-avdevice
BUILD_IN_SOURCE ON
+80
View File
@@ -0,0 +1,80 @@
# G-code preview while dragging
The sliced preview draws every toolpath segment of the plate as an instanced box. On a large
plate that is tens of millions of segments, and the frame is GPU-bound: the cost is the number of
instances drawn, not anything the CPU does per frame. Dragging the camera over such a plate cannot
keep up. The `preview_reduced_detail_mode` preference (*Graphics > G-code Preview*, off by
default) lets the preview draw less while the user drags and put the full toolpaths back when they
let go.
| Preference | Values | Effect |
|---|---|---|
| `preview_reduced_detail_mode` | `off`, `solid`, `shell` | what is drawn while dragging |
libvgcode (`src/libvgcode`) builds and binds the reduced toolpath set, `GCodeViewer` maps the
preferences onto it and draws the solid model, and `GLCanvas3D` decides when the user is dragging.
The OpenGL ES path keeps a single set and ignores the preference.
## Two sets, one walk
`ViewerImpl::update_enabled_entities()` walks the visible vertex range once and fills two segment
index buffers side by side: the **full** set and the **reduced** set (segments and options).
Building them together is what makes switching free: starting or ending a drag is a buffer
binding, never a rebuild. A change of mode does rebuild. Nothing is built while the mode
is off, and the reduced buffers are then uploaded empty so that the last set does not stay
allocated.
Whatever the mode leaves out, the bottom and top layers of the visible range are kept whole: they
are the faces the range cuts open, and the top is what the user is looking at.
### Modes
- `EndLayersOnly` (`solid` in the preference) keeps only the two end layers. `GCodeViewer` then
draws the sliced objects and the prime tower as opaque solids, see below.
- `ShellOnly` (`shell`) keeps every role but the sparse infill, internal solid infill, internal
bridge infill and gap fill, which lie under the skins or between the walls. Walls, top and
bottom surfaces, bridges, supports and the prime tower are drawn whole, so from outside the
print looks as it does at rest. The roles come from
the slicer, so the classification is exact wherever the slicer's is; what it cannot express is
the inside of the prime tower and of a support, which share one role with their outside and are
kept whole. A profile with no top or bottom shell layers, or no walls, leaves the infill on the
surface, so `GCodeViewer` turns the hiding off for such a print when it loads it, from the
print's default region settings; per-object overrides are not consulted.
## The solid model
The preview already loads the sliced objects as shells for its translucent ghost.
`GCodeViewer::render_solid_model()` draws those shells opaque, in their filament colors, with the
`gouraud` shader, whose z range cuts them to the visible layer range. The two toolpath layers of
the reduced set are drawn afterwards and cap the cut with what was really printed there. The
shells hold only the objects, so while this mode is on the prime tower is added from its sliced
mesh, positioned as the print placed it. It is added or removed on its own when the mode changes,
without reloading the objects, keeps its opaque color so that it never appears among the
translucent shells, and stays out of their bounding box. Supports have no mesh and are not shown,
and `load_shells()` drops every non-model-part volume, so a negative volume is not cut out.
A plate whose shells are not loaded keeps drawing toolpaths, since the solid model would leave
only the end layers.
## Deciding that the user is dragging
`GLCanvas3D::_update_preview_interaction()` runs at the top of every preview frame, before the
canvas decides whether to reuse its cached scene, so that the switch lands in that frame. Dragging
is `GLCanvas3D::is_user_interacting()`, the same answer the scene cache reads: the camera, the
navigator, a gizmo, the rectangle selection or either slider being held. A slider reports this from
ImGui's active id rather than its dirty flag, which is raised and consumed inside one frame. A
wheel step has no duration, so it holds the reduced set for a 150 ms settle time instead, and the
frame that restores the toolpaths is scheduled for when that time runs out, since the render timer
only wakes the idle loop. A drag cut short by focus or capture loss is ended explicitly, and a
button release wakes the idle loop, because on some platforms nothing else would until the next
input.
## Reused scene frames
`GLCanvas3D` keeps its last scene pass for frames that only rebuild the overlay (`SceneCache`). Its
key covers the canvas size, the camera and hover state, not what the toolpath sets draw, so a frame
that reuses the scene must never be one on which the set is switched.
`_update_preview_interaction()` therefore reports whether the bound set changed, and a frame on
which it did redraws the scene. The canvas neither captures nor reuses the scene while the user
drags, so no reduced frame outlives a drag, and the frame that ends a wheel's settle time is
requested as a full frame.
+19 -45
View File
@@ -28,8 +28,8 @@ Per-vendor granularity is what makes the system practical:
- A vendor whose profile is bumped invalidates only its own cache. The other 60-odd
vendors keep theirs — even when the bumped vendor is the shared Orca filament
library everyone else inherits from.
- The setup wizard loads its vendors through the same routine as startup, so it
gets the same speedup without a second code path.
- The setup wizard, which loads vendors one at a time, gets the same speedup as
startup without a second code path.
- A vendor with no cache, or a broken one, costs only that vendor a parse.
A cache holds *system* presets only. User presets, project settings and modified
@@ -162,20 +162,15 @@ cache nothing can invalidate is worse than no cache.
Vendors load in a fixed order, because filament inheritance crosses exactly one
boundary: any vendor's filament may inherit from the shared Orca filament library,
and nothing else reaches across vendors — an `include` is always vendor-local. Only
installing a vendor's presets crosses it; reading the vendor, from its cache or its
JSONs, needs nothing from the library. So every other vendor is read while the library
loads, each is installed against it as soon as both are done, and the results are
merged in a stable order:
and nothing else reaches across vendors — an `include` is always vendor-local. The
library therefore goes first, alone; every other vendor follows in parallel, resolving
against it; and the results are merged in a stable order:
```mermaid
flowchart LR
lib["1 · OrcaFilamentLibrary loaded;<br/>meanwhile every other vendor read<br/>from its cache or its JSONs"] --> par["2 · every other vendor installed<br/>in parallel, each into its own bundle,<br/>filaments resolving against the library"] --> merge["3 · bundles merged into one,<br/>in one pass per collection,<br/>in stable vendor order"]
lib["1 · OrcaFilamentLibrary<br/>loaded first, synchronously"] --> par["2 · every other vendor in parallel,<br/>each into its own bundle, filaments<br/>resolving against the loaded library"] --> merge["3 · bundles merged into one,<br/>sequentially, in stable vendor order"]
```
`PresetBundle::load_vendors` runs these steps for startup and for the setup wizard,
which hand it the vendors to load and the directory each is installed in.
Whether a vendor comes from its cache or from a parse changes nothing in that
order — both produce the same bundle, so cached and parsed vendors mix freely in
one startup.
@@ -216,12 +211,11 @@ shipped cache answered first, so the profile in `<data_dir>/system/` was never p
and its cache was never written back.
Serving from a cache is not a memory-image restore. The entries are deserialized and
then installed by `install_vendor`, the routine the JSON path hands the vendor's entries
to once it has parsed the sub-files: inheritance resolved against the presets installed
before them and the currently loaded filament library, includes layered in, configs
flattened onto the collection defaults, validated and registered. An `include` layers
what the included base states, between the parent and the preset's own keys: the base's
diff against the
then installed one by one — inheritance resolved against the presets installed before
them and the currently loaded filament library, includes layered in, configs flattened
onto the collection defaults, validated and registered — by the same function the JSON
path calls straight after parsing a sub-file. An `include` layers what the included
base states, between the parent and the preset's own keys: the base's diff against the
default, taken when the base itself was installed and before the per-variant padding
`inherits` sees, so only what a template sets reaches the presets including it. The two
paths share everything below the parse, which is what makes a cache-loaded bundle
@@ -229,16 +223,6 @@ indistinguishable from a JSON-loaded one by construction rather than by test cov
Installation also rebuilds each preset's file path from the local data directory, so a
shipped cache never carries the generating machine's paths.
Installing an entry is split in two. `resolve_vendor_preset` flattens it, reading only
what is registered under the names it inherits and includes, and `commit_vendor_preset`
registers it, the only step that writes anything shared. Entries resolve across threads
in runs and commit in the order the vendor lists them. A run ends before an entry that
inherits or includes one already in it, since that one's commit registers what the
entry resolves against, so no entry in a run reads what another in it registers. An
entry's parse messages are held until it commits. The bundle, the log's parse and
install messages and the error count therefore come out as parsing and installing one
entry at a time would leave them, whatever the listing order.
App upgrades work because a cache normally survives one. Only a deliberate
`CACHE_VERSION` bump makes an installed cache unreadable, and that is handled at
install time rather than at load: a vendor whose cache this build cannot read counts
@@ -270,20 +254,17 @@ the wizard caches the *derived JSON*, not another form of the inputs:
open, the wizard computes the current stamps (one version peek per vendor) and, when
they match, serves the catalog from the file — no bundle built, no preset installed.
Caching bundle inputs instead was tried and measured: rebuilding the bundle from
per-vendor caches costs over a second of preset installation whatever feeds it, so
only skipping the rebuild entirely wins.
per-vendor caches costs ~2 s of preset installation whatever feeds it, so only
skipping the rebuild entirely wins.
Any change to the set — a vendor added, removed or updated, or its cache-only
`.opc` replaced by a newer one — changes the stamps and retires the whole file;
the wizard then rebuilds the bundle with `PresetBundle::load_vendors`, the load
startup uses (per-vendor caches serving where they cover), and writes the catalog
back. When a vendor fails to load, the filament library included, that open falls
back to the wizard's own scan of the vendor JSONs, as when no bundle can be built,
and writes nothing. Selections, region and per-open decorations are applied
downstream of the cache either way, so a served catalog is indistinguishable from a
rebuilt one. Nothing ships this file and the updater never touches it; it is a
locally written artifact, re-derived whenever stale, written through a temp file and
rename so half a cache is never readable.
the wizard then rebuilds the bundle vendor by vendor (per-vendor caches serving where
they cover) and writes the catalog back. Selections, region and per-open decorations
are applied downstream of the cache either way, so a served catalog is
indistinguishable from a rebuilt one. Nothing ships this file and the updater never
touches it; it is a locally written artifact, re-derived whenever stale, written
through a temp file and rename so half a cache is never readable.
The cache lives under `<data_dir>/cache/`, not beside the vendors: everything that
scans `<data_dir>/system/` treats any `.opc` there as a vendor, so a non-vendor
@@ -397,13 +378,6 @@ enumerates only `*.json` will find no vendors at all in a packaged build.
the `CachedPreset` field list — written and read by `visit_entry` in
`PresetCacheFormat.cpp`, one list for the save, the load and the name peek alike — or
the cache's own layout or stamps, requires bumping `CACHE_VERSION` by hand.
- **Adding a kind of reference between presets**, as `inherits` and `include` are:
parse the names into `CachedPreset` (a field change, so `CACHE_VERSION` is bumped),
have `install_vendor_entries` end a run before an entry that names one already in it
and retain what the names point at, look them up only in `resolve_vendor_preset`, and
register what they point at only in `commit_vendor_preset`. The listing-order test in
`test_vendor_cache.cpp` fails for a kind the runs do not check once its fixture uses
it.
- **The dictionary indexes with a `uint16`**, so `print_config_def` may hold at most
65535 options and one cache at most 65535 distinct enum value names.
`CacheDictionary::save` throws past that, which surfaces when CI generates the
-240
View File
@@ -1,240 +0,0 @@
# Printer agents
Printer agents isolate printer-specific communication from the rest of OrcaSlicer. The GUI and
`DeviceManager` operate on a shared set of printer operations and device state; a selected printer
agent implements those operations for a particular printer ecosystem. The agent boundary allows
Bambu, Moonraker-based printers, built-in integrations, and Python-provided integrations to use the
same application workflow without making the GUI understand every printer protocol.
The current boundary is an adapter boundary around the existing application contract. In particular,
some request fields and message payloads still use the Bambu-shaped representation that existing
`MachineObject` and `DeviceManager` code consumes. The printer agent is responsible for translating
that representation into the protocol spoken by its printer. This is an intentional compatibility
constraint of the current design; the interface is not yet a neutral printer protocol.
The v1 dialect migration path is deliberately narrow. `DeviceManager` currently speaks the Bambu JSON
dialect because that is the payload shape already used throughout the command and state workflow. The
v1 `OrcaPrinterAgent` also accepts that Bambu dialect. Its transport path places the small translation
needed for the target printer at `deliver_to_sink`, keeping the compatibility code at the edge rather
than spreading it through `DeviceManager` or the agent interface.
The eventual direction is for `DeviceManager` to produce an Orca JSON dialect. The Bambu agent will then
own the translation from Orca JSON to Bambu's protocol, while `OrcaPrinterAgent` can forward the Orca
payload directly to its sink. The v1 translation at `deliver_to_sink` can then be removed without
changing `DeviceManager`, the command callers, or the rest of the agent workflow.
## Components
The system has four relevant layers:
```text
GUI / DeviceManager / MachineObject
|
NetworkAgent
/ \
IPrinterAgent ICloudServiceAgent
| |
printer protocol authentication and cloud services
```
### `DeviceManager` and `MachineObject`
`DeviceManager` owns the application-facing printer workflow. It maintains `MachineObject` instances,
updates their state, filters devices for the active printer agent, and initiates operations such as
homing, temperature changes, printing, subscriptions, and camera playback.
`MachineObject` remains the shared state model used by the GUI. It does not contain the implementation
of a printer protocol. When a device is discovered or returned by a cloud query, the device is tagged
with the active `printer_agent_id`. Device lists and selected-machine operations use that tag to avoid
sending an operation through an agent that does not own the device.
### `NetworkAgent`
`NetworkAgent` is the façade used by the GUI and `DeviceManager`. It owns:
- the currently selected `IPrinterAgent`;
- the registered cloud-service instances, indexed by provider;
- callbacks shared by the active printer agent and the application;
- the forwarding methods for printer commands and cloud operations.
There is one active printer agent for the currently selected printer preset. Switching the preset
increments the machine-list generation, disconnects the old printer agent, removes its callbacks, and
installs the newly selected agent. The façade then forwards printer operations to that agent.
Cloud operations are selected separately using a provider key. `NetworkAgent` forwards a cloud request
to the matching `ICloudServiceAgent`, and forwards cloud camera operations with a device ID. The
printer agent receives a cloud-agent pointer through `set_cloud_agent()` when it is created, allowing
printer communication to obtain cloud tokens without depending on a concrete cloud implementation.
### `IPrinterAgent`
`IPrinterAgent` is the printer-facing contract. It covers:
- cloud-relay and direct-LAN message delivery;
- LAN connection, discovery, binding, and certificates;
- printer subscriptions and callbacks;
- print operations;
- filament synchronization;
- camera capability and local camera URL reporting;
- printer command methods.
Concrete built-in implementations include the Bambu wrapper, the native Orca/Moonraker path, and
other printer-agent implementations registered by the application. A printer agent may use either
the cloud agent, a direct LAN connection, or both.
### `ICloudServiceAgent`
`ICloudServiceAgent` owns authentication and services provided by a cloud backend. It covers login
state, tokens, user and printer lists, settings synchronization, model services, cloud messages, and
cloud camera operations.
Cloud camera operations are device-scoped:
- `get_camera_url(dev_id, callback)` obtains a stream URL for one device;
- `create_camera_signaling_channel(dev_id)` creates signaling for one device where the provider
supports it.
This is separate from the local camera URL exposed by `IPrinterAgent`, which is currently scoped to
the active printer agent because a normal LAN agent represents one physical printer connection.
## Agent registration and selection
`NetworkAgentFactory` maintains the printer-agent registry. Each registry entry contains an agent ID,
a display name, and a factory function. Built-in agents register during application initialization.
Python printer-agent capabilities register dynamically and contribute an agent ID and factory entry.
The selected printer preset contains the printer-agent choice. If no explicit choice is stored, the
application preserves the existing default behavior: Bambu presets select the Bambu agent and other
presets select the native Orca agent. When a preset is changed, `GUI_App` resolves the effective agent
ID, obtains the corresponding cloud agent, creates the printer agent through the registry, and installs
it in `NetworkAgent`.
The registry rejects conflicting agent IDs. This matters for Python plugins because an agent ID is the
stable identity used by presets and device ownership; two enabled plugin capabilities must not claim
the same ID.
## Message and command flow
There are two low-level message paths:
- `send_message()` publishes a command through the printer's cloud relay;
- `send_message_to_printer()` sends a command directly to the printer over the LAN path.
Both paths accept a JSON string, quality-of-service and flag values, and return the existing network
status code domain. The agent owns the conversion from that JSON contract to its native transport.
The typed `command_*` methods are the application-facing convenience layer. The five generic defaults
currently implemented by `IPrinterAgent` construct the existing JSON dialect and route through the
same message path:
| Method | Default operation |
| --- | --- |
| `command_xyz_abs()` | Send `G90` for absolute positioning |
| `command_auto_leveling()` | Send `G29` for bed leveling |
| `command_go_home()` | Use the supported homing operation or send `G28` |
| `command_set_bed()` | Use the supported bed control or send `M140` |
| `command_set_nozzle()` | Send `M104` for nozzle temperature |
These are compatibility defaults for common printer workflows, not a guarantee that every firmware
implements every command identically. An agent can override a method when its protocol needs another
operation. For example, a Klipper configuration may use `BED_MESH_CALIBRATE` instead of `G29`.
The remaining common command methods default to `ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED` because their
existing behavior is vendor-specific or has no portable implementation:
- AMS RFID refresh;
- AMS calibration;
- AMS tray selection;
- camera start;
- axis control.
The methods remain on the common interface so an agent that supports them can override them explicitly.
`sequence_id` remains part of the command contract because `DeviceManager` creates and tracks it as
the command ID.
## Device ownership and stale responses
Printer-agent ownership is represented by `printer_agent_id` on device records and `MachineObject`
instances. The active agent ID is attached when a device is discovered, returned by a cloud list, or
reused after a preset switch. Local-machine configuration also persists the agent ID so a saved LAN
device is not silently reused by an unrelated agent.
Cloud printer-list responses carry three pieces of request context added by `NetworkAgent`:
```text
provider cloud provider used for the request
agent_id active printer agent when the request was made
generation machine-list generation when the request was made
```
`DeviceManager` accepts the response only when those values still match the current provider, active
agent, and generation. This prevents a slow response from the previous preset or provider from
repopulating the current device list.
The provider mapping is currently selected by `GUI_App`: the Bambu agent maps to the Bambu cloud
provider and other agents map to the Orca cloud provider. The generation check protects that existing
selection from races; it does not make cloud-provider ownership intrinsic to an agent. Cloud-printer
ownership and the broader Orca cloud services are therefore still separate architectural concerns.
## Python printer agents
`PrinterAgentPluginCapability` implements `IPrinterAgent` directly. The live capability object is
registered with `NetworkAgentFactory` and handed out as the printer agent when its agent ID is selected.
The plugin receives the selected `ICloudServiceAgent` through `set_cloud_agent()` just like a built-in
printer agent.
Python plugins must implement the core communication and lifecycle methods required by the interface,
including agent metadata, printer connection, discovery callbacks, and the two message-send methods.
Methods that are meaningful only to a particular printer are optional overrides where the C++ base
class provides a default.
All ten `command_*` methods are available in the Python binding and in the trampoline. Their override
status is intentionally optional:
- the five generic commands use the C++ default when Python does not override them;
- the five vendor-specific commands return `NOT_SUPPORTED` unless Python supplies an implementation;
- a Python implementation can replace either behavior for its own protocol.
The Python camera binding exposes HTTP, HTTPS, RTSP, and HTTP-snapshot modes. WebRTC remains a
built-in C++ camera mode, but is not exposed as a Python mode because the current Python capability
does not provide the corresponding cloud signaling-channel contract.
## Camera playback boundary
The camera stream mode describes how a stream is obtained; it does not by itself define ownership of
the wxWidgets view that renders it. `MediaPlayCtrl` selects and tears down the active backend, while
the wx parent owns the child window or renderer. This is important because a web view, native media
control, and frame-based/WebRTC renderer have different wx window-lifetime requirements.
Cloud URL and signaling requests are routed through `NetworkAgent` to the cloud provider selected for
the device. Local URL requests are routed to the active printer agent. The distinction keeps cloud
account services device-scoped while preserving the current one-LAN-agent/one-printer model.
## Compatibility constraints
The printer-agent boundary intentionally preserves several existing application contracts:
- Bambu-shaped JSON is still the shared command representation;
- existing network status codes are reused, with Orca-specific unsupported/capability errors added
in the Orca-reserved range;
- `MachineObject` remains the shared device-state model;
- preset and local-machine data retain compatibility with the existing agent-selection behavior;
- Python plugins use the existing capability and pybind11 registration system.
The agent abstraction is therefore responsible for containing vendor differences, not for pretending
that all vendor protocols are identical. The planned Orca JSON dialect is the protocol-neutral command
model for the `DeviceManager`/agent boundary. Once it is introduced, Bambu-specific translation remains
inside the Bambu agent and the Orca agent's v1 sink adapter can be removed as a self-contained cleanup.
## Main implementation locations
- [`IPrinterAgent`](../../src/slic3r/Utils/IPrinterAgent.hpp) — printer-agent contract and generic command defaults
- [`ICloudServiceAgent`](../../src/slic3r/Utils/ICloudServiceAgent.hpp) — cloud service and per-device
cloud camera contract
- [`NetworkAgent`](../../src/slic3r/Utils/NetworkAgent.hpp) — façade and dispatch between active agents
- [`NetworkAgentFactory`](../../src/slic3r/Utils/NetworkAgentFactory.hpp) — built-in and Python agent registry
- [`DeviceManager`](../../src/slic3r/GUI/DeviceCore/DevManager.cpp) — device ownership, filtering, and
stale-response checks
- [`PrinterAgentPluginCapability`](../../src/slic3r/plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.cpp)
— Python bindings
- [`MediaPlayCtrl`](../../src/slic3r/GUI/MediaPlayCtrl.cpp) — camera backend selection and playback lifecycle
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "OrcaFilamentLibrary",
"version": "02.04.00.17",
"version": "02.04.00.15",
"force_update": "0",
"description": "Orca Filament Library",
"filament_list": [
@@ -6,7 +6,7 @@
"filament_id": "OFvgE0Zh",
"instantiation": "false",
"fan_cooling_layer_time": [
"85"
"81"
],
"fan_min_speed": [
"50"
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"1"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"0"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"0"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
+1 -3
View File
@@ -575,7 +575,7 @@ function CapabilityCanRun(plugin, capability) {
}
function IsPluginChecked(plugin) {
return plugin.is_loaded;
return GetStatus(plugin) === "Activated";
}
function HasMixedCapabilityState(plugin) {
@@ -1347,8 +1347,6 @@ function StatusDescription(plugin) {
return "This plugin is still loading.";
case "Error":
return "This plugin is blocked until its error is fixed.";
case "RuntimeError":
return "This plugin is loaded but a capability reported an error.";
case "Inactive":
default:
return "This plugin is inactive. Activate it to install or load it.";
@@ -424,11 +424,6 @@ body.pane-resizing {
font-weight: 600;
}
.status-cell.status-runtimeerror {
color: var(--plugin-status-warn);
font-weight: 600;
}
.status-cell.status-loading {
color: var(--plugin-status-warn);
font-weight: 600;
@@ -685,11 +680,6 @@ body.pane-resizing {
color: var(--plugin-status-danger);
}
.detail-status-chip.status-runtimeerror {
background: var(--plugin-status-warn-bg);
color: var(--plugin-status-warn);
}
.detail-status-chip.status-loading {
background: var(--plugin-status-warn-bg);
color: var(--plugin-status-warn);
+30 -129
View File
@@ -214,9 +214,9 @@ function ShowModelInfo( pModel )
SendWXDebugInfo("Model Name: "+sModelName);
$('#ModelName').text(sModelName);
$('#ModelName').html(sModelName);
$('#ModelName').attr('title',sModelName);
$('#ModelAuthorName').text(sModelAuthor);
$('#ModelAuthorName').html(sModelAuthor);
switch(UploadType)
{
@@ -268,7 +268,7 @@ function ShowModelInfo( pModel )
break;
}
$('#Model_Desc').empty().append( SanitizeDescHtml( html_decode(sModelDesc) ) );
$('#Model_Desc').html( html_decode(sModelDesc) );
let ModelPreviewList=pModel.preview_img;
let TotalPreview=ModelPreviewList.length;
@@ -281,15 +281,16 @@ function ShowModelInfo( pModel )
if(TotalPreview>0)
{
$('#ModelPreviewList').empty();
let htmlPreview='';
for(let pn=0;pn<TotalPreview;pn++)
{
//let FTmpPath=decodeURIComponent(ModelPreviewList[pn]);
let FTmpPath=ModelPreviewList[pn]['filepath'];
$('#ModelPreviewList').append( $('<div class="swiper-slide"></div>').append( $('<img class="Model_PrevImg" />').attr('src',FTmpPath) ) );
htmlPreview+='<div class="swiper-slide"><img class="Model_PrevImg" src="'+FTmpPath+'" /></div>';
}
$('#ModelPreviewList').html(htmlPreview);
$('#Model_Preview_Image').viewer({
title: false,
fullsreen: false,
@@ -409,8 +410,7 @@ function ConstructFileHtml( ID, pItem )
{
let fTotal=pItem.length;
let pBoard=$('#'+ID+' .FileListBoard');
pBoard.empty();
let strHtml='';
for( let f=0;f<fTotal;f++ )
{
let pOne=pItem[f];
@@ -443,139 +443,39 @@ function ConstructFileHtml( ID, pItem )
ImgPath='img/default.png';
}
//Add html. File names come from the 3MF, so build the nodes rather than concatenating markup.
let pIconImg=$('<img />').attr('src',ImgPath);
let pMenu=$('<div class="FileMenu"><img src="img/s.svg" /></div>');
//Add html
if( strClass!='ImageIcon' )
{
pMenu.on('click', function(){ OnClickOpenFile(tPath); });
strHtml+='<div class="FileItem">'+
' <div class="'+strClass+'"><img src="'+ImgPath+'" /></div>'+
' <div class="FileText">'+
' <div class="FileName">'+tName+'</div>'+
' </div>'+
' <div class="FileMenu" onClick="OnClickOpenFile(\''+tPath+'\')"><img src="img/s.svg" /></div>'+
'</div>';
}
else
{
ImgID++;
let TmpImgID="AF"+ImgID;
pIconImg.attr('id',TmpImgID);
pMenu.on('click', function(){ OnClickOpenImage(TmpImgID); });
strHtml+='<div class="FileItem">'+
' <div class="'+strClass+'"><img id="'+TmpImgID+'" src="'+ImgPath+'" /></div>'+
' <div class="FileText">'+
' <div class="FileName">'+tName+'</div>'+
' </div>'+
' <div class="FileMenu" onClick="OnClickOpenImage(\''+TmpImgID+'\')"><img src="img/s.svg" /></div>'+
'</div>';
}
let pFileItem=$('<div class="FileItem"></div>');
pFileItem.append( $('<div></div>').addClass(strClass).append(pIconImg) );
pFileItem.append( $('<div class="FileText"></div>').append( $('<div class="FileName"></div>').text(tName).attr('title',tName) ) );
pFileItem.append( pMenu );
pBoard.append( pFileItem );
}
$('#'+ID+' .FileListBoard').html(strHtml);
if( fTotal>0 )
$('#'+ID).show();
}
// Descriptions are untrusted 3MF metadata that may carry rich-text HTML (e.g. from MakerWorld).
// Rebuild them from an inert parse, keeping only plain formatting tags and http(s) links and images.
var DescAllowedTags=['P','BR','B','STRONG','I','EM','U','S','STRIKE','DEL','INS','SUB','SUP','SMALL','MARK',
'Q','ABBR','KBD','WBR','H1','H2','H3','H4','H5','H6','UL','OL','LI','DL','DT','DD','BLOCKQUOTE','PRE','CODE',
'HR','SPAN','DIV','FIGURE','FIGCAPTION','TABLE','CAPTION','THEAD','TBODY','TFOOT','TR','TH','TD','A','IMG'];
// Plain attributes kept per tag; the numeric ones must be plain non-negative integers.
var DescAllowedAttrs={'IMG':['alt','title','width','height'],'TD':['colspan','rowspan'],'TH':['colspan','rowspan'],'OL':['start']};
var DescNumericAttrs=['width','height','colspan','rowspan','start'];
// Dropped together with their content; any other unknown tag is unwrapped to its children.
var DescDroppedTags=['SCRIPT','STYLE','TEMPLATE','NOSCRIPT','TEXTAREA','TITLE','IFRAME','FRAME','OBJECT','EMBED','SVG','MATH'];
function IsHttpUrl( strUrl )
{
// The scheme must be written as is, so nothing the URL parser would strip can precede or split it.
if( typeof strUrl!='string' || !/^https?:/i.test(strUrl) )
return false;
try
{
let sProtocol=new URL(strUrl).protocol;
return sProtocol=='http:' || sProtocol=='https:';
}
catch(e)
{
return false;
}
}
// Images load as soon as the page opens, so only https sources are kept: no plain-http requests to the local network.
function IsHttpsUrl( strUrl )
{
return IsHttpUrl(strUrl) && new URL(strUrl).protocol=='https:';
}
// Embedded YouTube players become a plain link to the video.
function GetYouTubeEmbedUrl( pNode )
{
let sSrc=pNode.getAttribute('src');
if( !IsHttpUrl(sSrc) )
return null;
let pUrl=new URL(sSrc);
return ( pUrl.origin=='https://www.youtube.com' && pUrl.pathname.indexOf('/embed/')==0 ) ? pUrl.href : null;
}
function CopyDescNodes( pSrc, pDst )
{
for( let pNode=pSrc.firstChild;pNode!=null;pNode=pNode.nextSibling )
{
if( pNode.nodeType==Node.TEXT_NODE )
{
pDst.appendChild( document.createTextNode(pNode.nodeValue) );
continue;
}
if( pNode.nodeType!=Node.ELEMENT_NODE )
continue;
let sTag=pNode.nodeName.toUpperCase();
if( sTag=='IFRAME' )
{
let sVideoUrl=GetYouTubeEmbedUrl(pNode);
if( sVideoUrl!=null )
{
let pLink=document.createElement('A');
pLink.setAttribute('href',sVideoUrl);
pLink.textContent=sVideoUrl;
pDst.appendChild(pLink);
}
continue;
}
if( $.inArray(sTag,DescDroppedTags)>=0 )
continue;
if( $.inArray(sTag,DescAllowedTags)<0 )
{
CopyDescNodes(pNode,pDst);
continue;
}
let pElem=document.createElement(sTag);
if( sTag=='A' && IsHttpUrl(pNode.getAttribute('href')) )
pElem.setAttribute('href',pNode.getAttribute('href'));
else if( sTag=='IMG' )
{
if( !IsHttpsUrl(pNode.getAttribute('src')) )
continue;
pElem.setAttribute('src',pNode.getAttribute('src'));
}
$.each( DescAllowedAttrs[sTag]||[], function(i,sAttr){
let sValue=pNode.getAttribute(sAttr);
if( sValue!=null && ( $.inArray(sAttr,DescNumericAttrs)<0 || /^\d+$/.test(sValue) ) )
pElem.setAttribute(sAttr,sValue);
});
CopyDescNodes(pNode,pElem);
pDst.appendChild(pElem);
}
}
function SanitizeDescHtml( strHtml )
{
let pFragment=document.createDocumentFragment();
// A DOMParser document is inert: it runs no scripts and loads no resources.
let pDoc=new DOMParser().parseFromString(strHtml,'text/html');
if( pDoc && pDoc.body )
CopyDescNodes(pDoc.body,pFragment);
return pFragment;
}
function ShowProfilelInfo( pProfile )
{
//==========Profile Info==========
@@ -583,10 +483,10 @@ function ShowProfilelInfo( pProfile )
let sProfileAuthor=decodeURIComponent(pProfile.author);
let sProfileDesc=decodeURIComponent(pProfile.description);
$('#ProfileName').text(sProfileName);
$('#ProfileAuthor').text(sProfileAuthor);
$('#ProfileName').html(sProfileName);
$('#ProfileAuthor').html(sProfileAuthor);
$('#Profile_Desc').empty().append( SanitizeDescHtml( html_decode(sProfileDesc) ) );
$('#Profile_Desc').html( html_decode(sProfileDesc) );
let ProfilePreviewList=pProfile.preview_img;
let TotalPreview=ProfilePreviewList.length;
@@ -599,14 +499,15 @@ function ShowProfilelInfo( pProfile )
if(TotalPreview>0)
{
$('#ProfilePreviewList').empty();
let htmlPreview='';
for(let pn=0;pn<TotalPreview;pn++)
{
let FTmpPath=ProfilePreviewList[pn]['filepath'];
$('#ProfilePreviewList').append( $('<div class="swiper-slide"></div>').append( $('<img class="Model_PrevImg" />').attr('src',FTmpPath) ) );
htmlPreview+='<div class="swiper-slide"><img class="Model_PrevImg" src="'+FTmpPath+'" /></div>';
}
$('#ProfilePreviewList').html(htmlPreview);
$('#Profile_Preview_Image').viewer({
title: false,
fullsreen: false,
@@ -331,15 +331,6 @@ modules:
sha256: deedcabe339165214a3637df4c86a507aef0d793cf8774ff68735f4737e8ddbc
dest: external-packages/FFMPEG
# libdatachannel v0.24.5
# The Git checkout includes the submodules that are missing from the
# GitHub source archive. DataChannel.cmake consumes it as SOURCE_DIR.
- type: git
url: https://github.com/paullouisageneau/libdatachannel.git
tag: v0.24.5
commit: 443f6934d9007eb7076ab7825ba330f355fcbead
dest: external-packages/DataChannel
# ---------------------------------------------------------------
# Fallback archives for deps normally provided by the GNOME SDK.
# These are only used if find_package() fails to locate them.
+6 -77
View File
@@ -2116,14 +2116,9 @@ int CLI::run(int argc, char **argv)
// One resolver for the whole run, so presets from the same vendor tree share its load.
std::unique_ptr<PresetBundle> system_preset_resolver;
auto ensure_system_preset_resolver = [&system_preset_resolver]() -> PresetBundle & {
if (!system_preset_resolver)
system_preset_resolver = std::make_unique<PresetBundle>();
return *system_preset_resolver;
};
auto resolve_preset = [&ensure_cli_preset_bundle, &ensure_system_preset_resolver](const std::string &file, DynamicPrintConfig &config,
std::string &config_type, const std::string &config_from,
bool probe_type, std::string &error) {
auto resolve_preset = [&ensure_cli_preset_bundle, &system_preset_resolver](const std::string &file, DynamicPrintConfig &config,
std::string &config_type, const std::string &config_from,
bool probe_type, std::string &error) {
const auto *inherits = config.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS);
if (!probe_type && (inherits == nullptr || inherits->value.empty()))
return true;
@@ -2131,7 +2126,9 @@ int CLI::run(int argc, char **argv)
PresetBundle *bundle = nullptr;
bool allow_source_manifest = false;
if (config_from == "system") {
bundle = &ensure_system_preset_resolver();
if (!system_preset_resolver)
system_preset_resolver = std::make_unique<PresetBundle>();
bundle = system_preset_resolver.get();
allow_source_manifest = true;
} else {
bundle = ensure_cli_preset_bundle(error);
@@ -3123,44 +3120,6 @@ int CLI::run(int argc, char **argv)
return 0;
};
// Load the project's printer and process settings as the GUI loads its presets: over the default preset,
// with every key the project does not list as changed, including keys saved before an option existed,
// taken from its current system preset.
auto load_project_preset = [this, &ensure_system_preset_resolver, &current_different_settings, filament_count](const std::string &system_name, Preset::Type type) {
if (system_name.empty())
return;
// Preset bookkeeping the CLI keeps in its own groups, e.g. inherits_group and print_compatible_printers.
static const std::set<std::string> bookkeeping_keys = {"inherits", "compatible_printers", "compatible_prints", "compatible_printers_condition",
"compatible_prints_condition", "print_settings_id", "printer_settings_id"};
const size_t index = type == Preset::TYPE_PRINTER ? filament_count + 1 : 0;
PresetBundle &resolver = ensure_system_preset_resolver();
DynamicPrintConfig system_config;
t_config_option_keys keys;
const DynamicPrintConfig config = Preset::load_external_config(type,
type == Preset::TYPE_PRINTER ? resolver.printers.default_preset_for(m_print_config).config : resolver.prints.default_preset().config,
m_print_config, PresetBundle::project_different_keys(index < current_different_settings.size() ? current_different_settings[index] : std::string()),
[&](const std::string &) -> DynamicPrintConfig * {
std::string error;
if (resolver.resolve_system_preset(system_config, type, system_name, config_substitution_rule, error))
return &system_config;
BOOST_LOG_TRIVIAL(warning) << boost::format("CLI: system preset '%1%' not resolved (%2%); the project keeps its values") % system_name % error;
return nullptr;
}, &keys);
for (const std::string &key : keys) {
const ConfigOption *opt = config.option(key);
const ConfigOption *old = m_print_config.option(key);
if (bookkeeping_keys.count(key) != 0 || opt == nullptr || (old != nullptr && *old == *opt))
continue;
BOOST_LOG_TRIVIAL(info) << boost::format("CLI: %1% from '%2%': %3% -> %4%") % key % system_name % (old ? old->serialize() : std::string("(missing)")) % opt->serialize();
m_print_config.set_key_value(key, opt->clone());
}
};
// The --uptodate path refreshes the project from its own system configs.
if (new_printer_name.empty() && load_machine_config.empty())
load_project_preset(current_printer_system_name, Preset::TYPE_PRINTER);
if (new_process_name.empty() && load_process_config.empty())
load_project_preset(current_process_system_name, Preset::TYPE_PRINT);
std::vector<std::string>& different_settings = m_print_config.option<ConfigOptionStrings>("different_settings_to_system", true)->values;
std::vector<std::string>& inherits_group = m_print_config.option<ConfigOptionStrings>("inherits_group", true)->values;
inherits_group.resize(filament_count + 2, std::string());
@@ -3517,14 +3476,6 @@ int CLI::run(int argc, char **argv)
}
new_variant_counts = old_variant_counts;
//filament_variant_count = old_variant_counts;
//ORCA: lay the per-variant options out one value per variant of the current filaments before each
// loaded filament replaces its own variants, including an option only a loaded filament
// defines, which otherwise starts as a single default value and never reaches the others.
for (const DynamicPrintConfig &config : load_filaments_config)
for (const std::string &opt_key : filament_options_with_variant)
if (opt_key != "filament_extruder_variant" && config.has(opt_key))
m_print_config.option(opt_key, true);
normalize_filament_values_to_variants(m_print_config);
for (int index = 0; index < load_filaments_config.size(); index++) {
DynamicPrintConfig& config = load_filaments_config[index];
int filament_index = load_filaments_index[index];
@@ -3696,25 +3647,6 @@ int CLI::run(int argc, char **argv)
}
}
//ORCA: a per-variant option the loaded filament does not define keeps the values of the
// variants the filament already had, and a variant new to it takes its first one's.
const int old_start = old_start_indice[filament_index - 1];
std::vector<int> kept_variant_indice = new_variant_indice;
for (int &i : kept_variant_indice)
if (i < 0)
i = old_start;
for (const std::string &opt_key : filament_options_with_variant) {
if (config.has(opt_key))
continue;
auto *opt_vec_dst = dynamic_cast<ConfigOptionVectorBase *>(m_print_config.option(opt_key));
if (opt_vec_dst == nullptr || opt_vec_dst->size() < size_t(old_start + old_variant_count))
continue;
// set_with_restore_2() pads its source in place
std::unique_ptr<ConfigOption> old_values(opt_vec_dst->clone());
opt_vec_dst->set_with_restore_2(static_cast<ConfigOptionVectorBase *>(old_values.get()), kept_variant_indice, old_start,
old_variant_count, true);
}
//update the old index
if (old_variant_count != new_variant_count)
{
@@ -4149,9 +4081,6 @@ int CLI::run(int argc, char **argv)
if (printer_technology == ptFFF) {
fff_print_config.apply(m_print_config, true);
m_print_config.apply(fff_print_config, true);
//ORCA: an option no preset or project defines has just come in as its single default value, and a
// command line override may hold one value per filament.
normalize_filament_values_to_variants(m_print_config);
} else {
boost::nowide::cerr << "invalid printer_technology " << std::endl;
record_exit_reson(outfile_dir, CLI_INVALID_PRINTER_TECH, 0, cli_errors[CLI_INVALID_PRINTER_TECH], sliced_info);
+7 -4
View File
@@ -205,6 +205,13 @@ void AppConfig::set_defaults()
if (get("seq_top_layer_only").empty())
set("seq_top_layer_only", "1");
// what the preview draws while the user drags it
{
const std::string mode = get("preview_reduced_detail_mode");
if (mode != "off" && mode != "solid" && mode != "shell")
set("preview_reduced_detail_mode", "off");
}
// ORCA: darken the layers the preview layer slider is not scrubbed to
if (get("preview_dim_previous_layers").empty())
set_bool("preview_dim_previous_layers", false);
@@ -511,10 +518,6 @@ void AppConfig::set_defaults()
set_bool("enable_high_low_temp_mixed_printing", false);
}
if (get("remember_print_action").empty()) {
set_bool("remember_print_action", false);
}
if (get("ignore_ext_filament_in_filament_map").empty()){
set_bool("ignore_ext_filament_in_filament_map", false);
}
-1
View File
@@ -355,7 +355,6 @@ set(lisbslic3r_sources
Optimize/Optimizer.hpp
Orient.cpp
Orient.hpp
ParallelResolve.hpp
ParameterUtils.cpp
ParameterUtils.hpp
pchheader.cpp
+13 -26
View File
@@ -849,19 +849,6 @@ ConfigSubstitutions ConfigBase::load_from_json(const std::string &file, ForwardC
return std::move(substitutions_ctxt.substitutions);
}
// Case-insensitive compare of a JSON key against a fixed ASCII one, without
// boost::iequals, whose std::locale() takes a lock the whole process shares in the
// MSVC runtime.
static bool ascii_iequals(const std::string &key, const char *literal)
{
auto lower = [](char c) { return (c >= 'A' && c <= 'Z') ? char(c - 'A' + 'a') : c; };
size_t i = 0;
for (; i < key.size() && literal[i] != '\0'; ++ i)
if (lower(key[i]) != lower(literal[i]))
return false;
return i == key.size() && literal[i] == '\0';
}
int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContext& substitution_context, bool load_inherits_to_config, std::map<std::string, std::string>& key_values, std::string& reason)
{
json j;
@@ -929,44 +916,44 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex
}
//parse the json elements
for (auto it = j.begin(); it != j.end(); it++) {
if (ascii_iequals(it.key(), BBL_JSON_KEY_VERSION)) {
if (boost::iequals(it.key(),BBL_JSON_KEY_VERSION)) {
key_values.emplace(BBL_JSON_KEY_VERSION, it.value());
}
else if (ascii_iequals(it.key(), BBL_JSON_KEY_IS_CUSTOM)) {
else if (boost::iequals(it.key(), BBL_JSON_KEY_IS_CUSTOM)) {
//skip it
}
else if (ascii_iequals(it.key(), BBL_JSON_KEY_NAME)) {
else if (boost::iequals(it.key(), BBL_JSON_KEY_NAME)) {
key_values.emplace(BBL_JSON_KEY_NAME, it.value());
if (it.value() == "project_settings")
is_project_settings = true;
}
else if (ascii_iequals(it.key(), BBL_JSON_KEY_URL)) {
else if (boost::iequals(it.key(), BBL_JSON_KEY_URL)) {
key_values.emplace(BBL_JSON_KEY_URL, it.value());
}
else if (ascii_iequals(it.key(), BBL_JSON_KEY_TYPE)) {
else if (boost::iequals(it.key(), BBL_JSON_KEY_TYPE)) {
key_values.emplace(BBL_JSON_KEY_TYPE, it.value());
}
else if (ascii_iequals(it.key(), BBL_JSON_KEY_SETTING_ID)) {
else if (boost::iequals(it.key(), BBL_JSON_KEY_SETTING_ID)) {
key_values.emplace(BBL_JSON_KEY_SETTING_ID, it.value());
}
else if (ascii_iequals(it.key(), BBL_JSON_KEY_FILAMENT_ID)) {
else if (boost::iequals(it.key(), BBL_JSON_KEY_FILAMENT_ID)) {
key_values.emplace(BBL_JSON_KEY_FILAMENT_ID, it.value());
}
else if (ascii_iequals(it.key(), BBL_JSON_KEY_FROM)) {
else if (boost::iequals(it.key(), BBL_JSON_KEY_FROM)) {
key_values.emplace(BBL_JSON_KEY_FROM, it.value());
}
else if (ascii_iequals(it.key(), BBL_JSON_KEY_DESCRIPTION)) {
else if (boost::iequals(it.key(), BBL_JSON_KEY_DESCRIPTION)) {
key_values.emplace(BBL_JSON_KEY_DESCRIPTION, it.value());
}
else if (ascii_iequals(it.key(), BBL_JSON_KEY_INSTANTIATION)) {
else if (boost::iequals(it.key(), BBL_JSON_KEY_INSTANTIATION)) {
key_values.emplace(BBL_JSON_KEY_INSTANTIATION, it.value());
}
else if (!load_inherits_to_config && ascii_iequals(it.key(), BBL_JSON_KEY_INHERITS)) {
else if (!load_inherits_to_config && boost::iequals(it.key(), BBL_JSON_KEY_INHERITS)) {
key_values.emplace(BBL_JSON_KEY_INHERITS, it.value());
}
else if (!load_inherits_to_config && ascii_iequals(it.key(), BBL_JSON_KEY_INCLUDES)) {
else if (!load_inherits_to_config && boost::iequals(it.key(), BBL_JSON_KEY_INCLUDES)) {
key_values.emplace(BBL_JSON_KEY_INCLUDES, it.value().dump());
} else if (ascii_iequals(it.key(), ORCA_JSON_KEY_RENAMED_FROM)) {
} else if (boost::iequals(it.key(), ORCA_JSON_KEY_RENAMED_FROM)) {
key_values.emplace(ORCA_JSON_KEY_RENAMED_FROM, it.value());
} else {
t_config_option_key opt_key = it.key();
+8 -12
View File
@@ -141,7 +141,7 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
its.indices.emplace_back(indices[0], indices[1], indices[2]);
int face_index =its.indices.size() - 1;
RGBA face_color;
auto set_face_color = [&uvs, &data, &mtl_data, &obj_info, &face_color](int face_index, const std::string mtl_name, const std::array<int, 3> &corners) {
auto set_face_color = [&uvs, &data, &mtl_data, &obj_info, &face_color](int face_index, const std::string mtl_name) {
if (mtl_data.new_mtl_unmap.find(mtl_name) != mtl_data.new_mtl_unmap.end()) {
bool is_merge_ka_kd = true;
for (size_t n = 0; n < 3; n++) {
@@ -174,7 +174,7 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
return Vec2f::Zero();
return Vec2f(data.textureCoordinates[idx * OBJ_TEXCOORD_LENGTH], data.textureCoordinates[idx * OBJ_TEXCOORD_LENGTH + 1]);
};
std::array<Vec2f, 3> uv_array{uv_at(uvs[corners[0]]), uv_at(uvs[corners[1]]), uv_at(uvs[corners[2]])};
std::array<Vec2f, 3> uv_array{uv_at(uvs[0]), uv_at(uvs[1]), uv_at(uvs[2])};
obj_info.uvs.emplace_back(uv_array);
}
obj_info.face_colors.emplace_back(face_color);
@@ -185,27 +185,27 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
}
}
};
auto set_face_color_by_mtl = [&data, &set_face_color](int face_index, const std::array<int, 3> &corners) {
auto set_face_color_by_mtl = [&data, &set_face_color](int face_index) {
if (data.usemtls.size() == 1) {
set_face_color(face_index, data.usemtls[0].name, corners);
set_face_color(face_index, data.usemtls[0].name);
} else {
for (size_t k = 0; k < data.usemtls.size(); k++) {
auto mtl = data.usemtls[k];
if (face_index >= mtl.face_start && face_index <= mtl.face_end) {
set_face_color(face_index, data.usemtls[k].name, corners);
set_face_color(face_index, data.usemtls[k].name);
break;
}
}
}
};
if (exist_mtl) {
set_face_color_by_mtl(face_index, {0, 1, 2});
set_face_color_by_mtl(face_index);
}
if (cnt == 4) {
its.indices.emplace_back(indices[0], indices[2], indices[3]);
int face_index = its.indices.size() - 1;
if (exist_mtl) {
set_face_color_by_mtl(face_index, {0, 2, 3});
set_face_color_by_mtl(face_index);
}
}
}
@@ -217,12 +217,8 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
message = _L("This OBJ file couldn't be read because it's empty.");
return false;
}
if (meshptr->volume() < 0) {
if (meshptr->volume() < 0)
meshptr->flip_triangles();
// Flipping swaps corners 1 and 2 of every face, so the UVs have to follow.
for (std::array<Vec2f, 3> &uv : obj_info.uvs)
std::swap(uv[1], uv[2]);
}
// Hand the parsed material table back so callers can build a TexturedMesh from it.
if (out_mtl)
*out_mtl = mtl_data;
+89 -71
View File
@@ -1497,7 +1497,12 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
check_add_eol(toolchange_gcode_str);
// SoftFever: set new PA for new filament
gcode += gcodegen.set_filament_pressure_advance(new_filament_id);
if (gcodegen.config().enable_pressure_advance.get_at(new_filament_id)) {
gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_filament_id));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
gcodegen.m_pa_processor->resetPreviousPA(gcodegen.config().pressure_advance.get_at(new_filament_id));
}
// A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>());
@@ -1821,8 +1826,12 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
check_add_eol(toolchange_gcode_str);
// SoftFever: set new PA for new filament
if (new_extruder_id != -1)
gcode += gcodegen.set_filament_pressure_advance(new_extruder_id);
if (new_extruder_id != -1 && gcodegen.config().enable_pressure_advance.get_at(new_extruder_id)) {
gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_extruder_id));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
gcodegen.m_pa_processor->resetPreviousPA(gcodegen.config().pressure_advance.get_at(new_extruder_id));
}
// A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>());
@@ -3223,7 +3232,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
m_cooling_buffer->set_current_extruder(initial_extruder_id, extruder_id);
// Orca: Initialise AdaptivePA processor filter
m_pa_processor = std::make_unique<AdaptivePAProcessor>(*this);
m_pa_processor = std::make_unique<AdaptivePAProcessor>(*this, tool_ordering.all_extruders());
// Update output variables after the extruders were initialized.
m_placeholder_parser_integration.init(m_writer);
@@ -3788,7 +3797,12 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
}
}
// Orca: add missing PA settings for initial filament
file.write(set_filament_pressure_advance(initial_non_support_extruder_id));
if (m_config.enable_pressure_advance.get_at(initial_non_support_extruder_id)) {
file.write(m_writer.set_pressure_advance(m_config.pressure_advance.get_at(initial_non_support_extruder_id)));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(initial_non_support_extruder_id));
}
}
//flush FanMover buffer to avoid modifying the start gcode if it's manual.
@@ -4174,28 +4188,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
print.throw_if_canceled();
// Some firmwares only scan the last N lines for the time estimate, so the stats are written
// after the config block.
if (!is_bbl_printers && !skip_config_block) {
file.write("; CONFIG_BLOCK_START\n");
std::string full_config;
append_full_config(print, full_config);
if (!full_config.empty())
file.write(full_config);
// SoftFever: write compatiple info
int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type);
file.write_format("; first_layer_bed_temperature = %d\n", first_layer_bed_temperature);
file.write_format("; bed_shape = %s\n", print.full_print_config().opt_serialize("printable_area").c_str());
file.write_format("; first_layer_temperature = %d\n", print.config().nozzle_temperature_initial_layer.get_at(0));
file.write_format("; first_layer_height = %.3f\n", print.config().initial_layer_print_height.value);
//SF TODO
// file.write_format("; variable_layer_height = %d\n", print.ad.adaptive_layer_height ? 1 : 0);
file.write("; CONFIG_BLOCK_END\n\n");
}
// Get filament stats.
file.write(DoExport::update_print_stats_and_format_filament_stats(
// Const inputs
@@ -4222,6 +4214,26 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder)
.c_str());
file.write("\n");
if (!skip_config_block) {
file.write("; CONFIG_BLOCK_START\n");
std::string full_config;
append_full_config(print, full_config);
if (!full_config.empty())
file.write(full_config);
// SoftFever: write compatiple info
int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type);
file.write_format("; first_layer_bed_temperature = %d\n", first_layer_bed_temperature);
file.write_format("; bed_shape = %s\n", print.full_print_config().opt_serialize("printable_area").c_str());
file.write_format("; first_layer_temperature = %d\n", print.config().nozzle_temperature_initial_layer.get_at(0));
file.write_format("; first_layer_height = %.3f\n", print.config().initial_layer_print_height.value);
//SF TODO
// file.write_format("; variable_layer_height = %d\n", print.ad.adaptive_layer_height ? 1 : 0);
file.write("; CONFIG_BLOCK_END\n\n");
} // !skip_config_block
}
file.write("\n");
@@ -8378,16 +8390,12 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// Orca: Dynamic PA
// If adaptive PA is enabled, by default evaluate PA on all extrusion moves
// The PA settings and the PA_CHANGE tags use the filament config index of the filament's extruder variant
const unsigned int pa_config_index = (unsigned int) get_filament_config_index(m_writer.filament()->id());
const bool adaptive_pa = m_config.adaptive_pressure_advance.get_at(pa_config_index) && m_config.enable_pressure_advance.get_at(pa_config_index);
const bool adaptive_pa_overhangs = adaptive_pa && m_config.adaptive_pressure_advance_overhangs.get_at(pa_config_index);
bool is_pa_calib = m_curr_print->calib_mode() == CalibMode::Calib_PA_Line ||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Pattern ||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower;
bool evaluate_adaptive_pa = false;
bool role_change = (m_last_extrusion_role != path.role());
if (!is_pa_calib && adaptive_pa) {
if (!is_pa_calib && FILAMENT_CONFIG(adaptive_pressure_advance) && FILAMENT_CONFIG(enable_pressure_advance)) {
evaluate_adaptive_pa = true;
// If we have already emmited a PA change because the m_multi_flow_segment_path_pa_set is set
// skip re-issuing the PA change tag.
@@ -8470,7 +8478,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// Post processor flag generation code segment when option to emit only at role changes is enabled
// Variables published to the post processor:
// 1) Tag to trigger a PA evaluation (because a role change was identified and the user has requested dynamic PA adjustments)
// 2) Current filament config index (to identify the PA model for the currently used filament and its extruder variant)
// 2) Current extruder ID (to identify the PA model for the currently used extruder)
// 3) mm3_per_mm value (to then multiply by the final model print speed after slowdown for cooling is applied)
// 4) the current acceleration (to pass to the model for evaluation)
// 5) whether this is an external perimeter (for future use)
@@ -8481,7 +8489,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
if (m_multi_flow_segment_path_average_mm3_per_mm > 0) {
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index,
m_writer.filament()->id(),
m_multi_flow_segment_path_average_mm3_per_mm,
acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8494,7 +8502,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// to issue a zero flow PA change command for this
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index,
m_writer.filament()->id(),
_mm3_per_mm,
acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8602,7 +8610,11 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// or a flow change, so emit the flag to evaluate PA for the upcomming extrusion
// Emit tag before new speed is set so the post processor reads the next speed immediately and uses it.
// Dont emit tag if it has just already been emitted from a role change above
if(_mm3_per_mm >0 && adaptive_pa_overhangs && !evaluate_adaptive_pa){
if(_mm3_per_mm >0 &&
FILAMENT_CONFIG(adaptive_pressure_advance) &&
FILAMENT_CONFIG(enable_pressure_advance) &&
FILAMENT_CONFIG(adaptive_pressure_advance_overhangs) &&
!evaluate_adaptive_pa){
if(writer().get_current_speed() > F){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion
if(m_config.gcode_comments){
sprintf(buf, "; Ramp down-non-variable\n");
@@ -8610,7 +8622,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
}
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index,
m_writer.filament()->id(),
_mm3_per_mm,
acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8625,7 +8637,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
}
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index,
m_writer.filament()->id(),
_mm3_per_mm,
acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8831,7 +8843,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// ORCA: Adaptive PA code segment when adjusting PA within the same feature
// There is a speed change or flow change so emit the flag to evaluate PA for the upcomming extrusion
// Emit tag before new speed is set so the post processor reads the next speed immediately and uses it.
if(_mm3_per_mm >0 && adaptive_pa_overhangs){
if(_mm3_per_mm >0 &&
EXTRUDER_CONFIG(adaptive_pressure_advance) &&
EXTRUDER_CONFIG(enable_pressure_advance) &&
EXTRUDER_CONFIG(adaptive_pressure_advance_overhangs) ){
if(last_set_speed > new_speed){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion
if(m_config.gcode_comments) {
sprintf(buf, "; Ramp up-variable\n");
@@ -8839,7 +8854,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
}
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index,
m_writer.filament()->id(),
_mm3_per_mm,
acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8854,7 +8869,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
}
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index,
m_writer.filament()->id(),
_mm3_per_mm,
acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -9407,29 +9422,33 @@ void GCode::update_placeholder_parser_with_variant_params()
if (num_filaments == 0)
return;
// Helper: remap config arrays from variant index space to filament_id index space.
// Helpers: remap config arrays from variant index space to filament_id index space.
// After remapping, gcode templates can use param[filament_id] directly.
auto remap_by_filament = [&](const auto &src) {
std::decay_t<decltype(src.values)> dst(num_filaments);
auto remap_floats_by_filament = [&](const auto &src) {
std::vector<double> dst(num_filaments);
for (size_t i = 0; i < num_filaments; ++i)
dst[i] = src.get_at(get_filament_config_index(i));
return dst;
};
auto remap_ints_by_filament = [&](const auto &src) {
std::vector<int> dst(num_filaments);
for (size_t i = 0; i < num_filaments; ++i)
dst[i] = src.get_at(get_filament_config_index(i));
return dst;
};
// --- filament_options_with_variant: gcode indexes by filament_id ---
this->placeholder_parser().set("filament_max_volumetric_speed", new ConfigOptionFloats(remap_by_filament(m_config.filament_max_volumetric_speed)));
this->placeholder_parser().set("filament_pre_cooling_temperature", new ConfigOptionInts(remap_by_filament(m_config.filament_pre_cooling_temperature)));
this->placeholder_parser().set("filament_pre_cooling_temperature_nc", new ConfigOptionInts(remap_by_filament(m_config.filament_pre_cooling_temperature_nc)));
this->placeholder_parser().set("filament_cooling_before_tower", new ConfigOptionFloats(remap_by_filament(m_config.filament_cooling_before_tower)));
this->placeholder_parser().set("nozzle_temperature_initial_layer", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature_initial_layer)));
this->placeholder_parser().set("nozzle_temperature", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature)));
this->placeholder_parser().set("filament_max_volumetric_speed", new ConfigOptionFloats(remap_floats_by_filament(m_config.filament_max_volumetric_speed)));
this->placeholder_parser().set("filament_pre_cooling_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.filament_pre_cooling_temperature)));
this->placeholder_parser().set("filament_pre_cooling_temperature_nc", new ConfigOptionInts(remap_ints_by_filament(m_config.filament_pre_cooling_temperature_nc)));
this->placeholder_parser().set("filament_cooling_before_tower", new ConfigOptionFloats(remap_floats_by_filament(m_config.filament_cooling_before_tower)));
this->placeholder_parser().set("nozzle_temperature_initial_layer", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature_initial_layer)));
this->placeholder_parser().set("nozzle_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature)));
// first_layer_temperature is a legacy alias of nozzle_temperature_initial_layer
this->placeholder_parser().set("first_layer_temperature", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature_initial_layer)));
this->placeholder_parser().set("pressure_advance", new ConfigOptionFloats(remap_by_filament(m_config.pressure_advance)));
this->placeholder_parser().set("enable_pressure_advance", new ConfigOptionBools(remap_by_filament(m_config.enable_pressure_advance)));
this->placeholder_parser().set("first_layer_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature_initial_layer)));
// --- printer_options_with_variant_1: in m_config these are already merged as filament-indexed ---
this->placeholder_parser().set("retraction_distances_when_cut", new ConfigOptionFloats(remap_by_filament(m_config.retraction_distances_when_cut)));
this->placeholder_parser().set("retraction_distances_when_cut", new ConfigOptionFloats(remap_floats_by_filament(m_config.retraction_distances_when_cut)));
// hotend_cooling_rate / hotend_heating_rate: gcode uses [filament_map[x]-1] (extruder_id), no remap needed
// --- filament_map: per-layer dynamic, sync from m_config to placeholder_parser ---
@@ -9439,10 +9458,10 @@ void GCode::update_placeholder_parser_with_variant_params()
{
// Fast purge mode uses filament_flush_temp_fast; Default is inert.
bool use_fast_flush = m_config.prime_volume_mode == PrimeVolumeMode::pvmFast;
auto flush_v_speed = remap_by_filament(m_config.filament_flush_volumetric_speed);
auto filament_max_v = remap_by_filament(m_config.filament_max_volumetric_speed);
auto flush_temps = remap_by_filament(use_fast_flush ? m_config.filament_flush_temp_fast
: m_config.filament_flush_temp);
auto flush_v_speed = remap_floats_by_filament(m_config.filament_flush_volumetric_speed);
auto filament_max_v = remap_floats_by_filament(m_config.filament_max_volumetric_speed);
auto flush_temps = remap_ints_by_filament(use_fast_flush ? m_config.filament_flush_temp_fast
: m_config.filament_flush_temp);
for (size_t i = 0; i < num_filaments; ++i) {
if (flush_v_speed[i] == 0)
flush_v_speed[i] = filament_max_v[i];
@@ -9494,7 +9513,12 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config);
check_add_eol(gcode);
}
gcode += set_filament_pressure_advance(new_filament_id);
if (m_config.enable_pressure_advance.get_at(new_filament_id)) {
gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(new_filament_id));
}
gcode += m_writer.toolchange(new_filament_id, new_extruder_id);
if (Extruder *fil = m_writer.filament())
@@ -9885,24 +9909,18 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
if (m_ooze_prevention.enable && !defer_temp_wait)
gcode += m_ooze_prevention.post_toolchange(*this);
gcode += set_filament_pressure_advance(new_filament_id);
if (m_config.enable_pressure_advance.get_at(new_filament_id)) {
gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(new_filament_id));
}
//Orca: tool changer or IDEX's firmware may change Z position, so we set it to unknown/undefined
m_last_pos_defined = false;
return gcode;
}
std::string GCode::set_filament_pressure_advance(unsigned int filament_id)
{
const size_t fi = get_filament_config_index(filament_id);
if (!m_config.enable_pressure_advance.get_at(fi))
return {};
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(fi));
return m_writer.set_pressure_advance(m_config.pressure_advance.get_at(fi));
}
inline std::string polygon_to_string(const Polygon &polygon, Print *print, bool is_print_space = false) {
std::ostringstream gcode;
gcode << "[";
-2
View File
@@ -281,8 +281,6 @@ public:
// extra_retract forwards a PETG pre-extrusion over-extrusion; default 0 -> identical to the plain deretract.
std::string unretract(float extra_retract = 0.f) { return m_writer.unlift() + m_writer.unretract(extra_retract); }
std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1, bool defer_temp_wait = false);
// Sets the pressure advance of the filament's extruder variant, if enabled for it.
std::string set_filament_pressure_advance(unsigned int filament_id);
bool is_BBL_Printer();
WipeTowerType wipe_tower_type();
+33 -26
View File
@@ -21,30 +21,38 @@ namespace Slic3r {
*
* @param gcodegen A reference to the GCode object that generates the G-code.
*/
AdaptivePAProcessor::AdaptivePAProcessor(GCode &gcodegen)
AdaptivePAProcessor::AdaptivePAProcessor(GCode &gcodegen, const std::vector<unsigned int> &tools_used)
: m_gcodegen(gcodegen),
m_config(gcodegen.config()),
m_last_predicted_pa(0.0),
m_max_next_feedrate(0.0),
m_next_feedrate(0.0),
m_current_feedrate(0.0),
m_last_config_index(-1),
m_last_extruder_id(-1),
m_pa_change_pattern(R"(; PA_CHANGE:T(\d+) MM3MM:([0-9]*\.[0-9]+) ACCEL:(\d+) BR:(\d+) RC:(\d+) OV:(\d+))"),
m_g1_f_pattern(R"(G1 F([0-9]+))")
{
// Constructor body can be used for further initialization if necessary
for (unsigned int tool : tools_used) {
// Only enable model for the tool if both PA and adaptive PA options are enabled
if(m_config.adaptive_pressure_advance.get_at(tool) && m_config.enable_pressure_advance.get_at(tool)){
auto interpolator = std::make_unique<AdaptivePAInterpolator>();
// Get calibration values from extruder
std::string pa_calibration_values = m_config.adaptive_pressure_advance_model.get_at(tool);
// Setup the model and store it in the tool-interpolation model map
interpolator->parseAndSetData(pa_calibration_values);
m_AdaptivePAInterpolators[tool] = std::move(interpolator);
}
}
}
// Method to get the interpolator for a specific filament config index.
// The model is built the first time an index is requested, as the indices in use depend on
// the extruder variant each filament prints with.
AdaptivePAInterpolator* AdaptivePAProcessor::getInterpolator(unsigned int config_index) {
auto [it, inserted] = m_AdaptivePAInterpolators.try_emplace(config_index);
// Only enable model for the index if both PA and adaptive PA options are enabled
if (inserted && m_config.adaptive_pressure_advance.get_at(config_index) && m_config.enable_pressure_advance.get_at(config_index)) {
it->second = std::make_unique<AdaptivePAInterpolator>();
it->second->parseAndSetData(m_config.adaptive_pressure_advance_model.get_at(config_index));
// Method to get the interpolator for a specific tool ID
AdaptivePAInterpolator* AdaptivePAProcessor::getInterpolator(unsigned int tool_id) {
auto it = m_AdaptivePAInterpolators.find(tool_id);
if (it != m_AdaptivePAInterpolators.end()) {
return it->second.get();
}
return it->second.get();
return nullptr; // Handle the case where the tool_id was not found
}
/**
@@ -100,17 +108,16 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
// the PA for that material is set. As no tag below will be found for this extruder, the original PA is retained.
if (line.find("; PA_CHANGE") == 0) { // prune lines quickly before running regex check as regex is more expensive to run
if (std::regex_search(line, m_match, m_pa_change_pattern)) {
// The tag carries the filament config index, which selects the PA settings of the filament's extruder variant
int config_index = std::stoi(m_match[1].str());
int extruder_id = std::stoi(m_match[1].str());
mm3mm_value = std::stod(m_match[2].str());
accel_value = std::stod(m_match[3].str());
int isBridge = std::stoi(m_match[4].str());
int roleChange = std::stoi(m_match[5].str());
int isOverhang = std::stoi(m_match[6].str());
// Check if the filament config index has changed
bool config_index_changed = (config_index != m_last_config_index);
m_last_config_index = config_index;
// Check if the extruder ID has changed
bool extruder_changed = (extruder_id != m_last_extruder_id);
m_last_extruder_id = extruder_id;
// Save the PA_CHANGE line to output later after finding feedrate
pa_change_line = line;
@@ -205,23 +212,23 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
// Calculate the predicted PA using the upcomming feature maximum feedrate
// Get the interpolator for the active tool
AdaptivePAInterpolator* interpolator = getInterpolator(m_last_config_index);
AdaptivePAInterpolator* interpolator = getInterpolator(m_last_extruder_id);
double predicted_pa = 0;
double adaptive_PA_speed = 0;
if(!interpolator){ // Tool not found in the interpolator map
// Tool not found in the PA interpolator to tool map
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0;
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0;
if(m_config.gcode_comments) output << "; APA: Tool doesnt have APA enabled\n";
} else if (!interpolator->isInitialised() || (!m_config.adaptive_pressure_advance.get_at(m_last_config_index)) )
} else if (!interpolator->isInitialised() || (!m_config.adaptive_pressure_advance.get_at(m_last_extruder_id)) )
// Check if the model is not initialised by the constructor for the active extruder
// Also check that adaptive PA is enabled for that extruder. This should not be needed
// as the PA change flag should not be set upstream (in the GCode.cpp file) if adaptive PA is disabled
// however check for robustness sake.
{
// Model failed or adaptive pressure advance not enabled - use default value from m_config
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0;
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0;
if(m_config.gcode_comments) output << "; APA: Interpolator setup failed, using default pressure advance\n";
} else { // Model setup succeeded
// Proceed to identify the print speed to use to calculate the adaptive PA value
@@ -242,18 +249,18 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
predicted_pa = (*interpolator)(mm3mm_value * adaptive_PA_speed, accel_value);
// This is a bridge, use the dedicated PA setting.
if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index) > EPSILON)
predicted_pa = m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index);
if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id) > EPSILON)
predicted_pa = m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id);
if (predicted_pa < 0) { // If extrapolation fails, fall back to the default PA for the extruder.
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0;
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0;
if(m_config.gcode_comments) output << "; APA: Interpolation failed, using fallback pressure advance value\n";
}
}
if(m_config.gcode_comments) {
// Output debug GCode comments
output << pa_change_line << '\n'; // Output PA change command tag
if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index) > EPSILON)
if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id) > EPSILON)
output << "; APA Model Override (bridge)\n";
output << "; APA Current Speed: " << std::to_string(m_current_feedrate) << "\n";
output << "; APA Next Speed: " << std::to_string(m_next_feedrate) << "\n";
@@ -262,7 +269,7 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
output << "; APA Flow rate: " << std::to_string(mm3mm_value * m_max_next_feedrate) << "\n";
output << "; APA Prev PA: " << std::to_string(m_last_predicted_pa) << " New PA: " << std::to_string(predicted_pa) << "\n";
}
if (config_index_changed || std::fabs(predicted_pa - m_last_predicted_pa) > EPSILON) {
if (extruder_changed || std::fabs(predicted_pa - m_last_predicted_pa) > EPSILON) {
output << m_gcodegen.writer().set_pressure_advance(predicted_pa); // Use m_writer to set pressure advance
m_last_predicted_pa = predicted_pa; // Update the last predicted PA value
}
+11 -7
View File
@@ -11,6 +11,7 @@
#include <regex>
#include <memory>
#include <map>
#include <vector>
#include "AdaptivePAInterpolator.hpp"
namespace Slic3r {
@@ -32,7 +33,7 @@ public:
*
* @param gcodegen A reference to the GCode object that generates the G-code.
*/
AdaptivePAProcessor(GCode &gcodegen);
AdaptivePAProcessor(GCode &gcodegen, const std::vector<unsigned int> &tools_used);
/**
* @brief Processes a layer of G-code and applies adaptive pressure advance.
@@ -69,25 +70,28 @@ public:
private:
GCode &m_gcodegen; ///< Reference to the GCode object.
std::unordered_map<unsigned int, std::unique_ptr<AdaptivePAInterpolator>> m_AdaptivePAInterpolators; ///< Map between Interpolator objects and filament config indices (null when adaptive PA is off)
std::unordered_map<unsigned int, std::unique_ptr<AdaptivePAInterpolator>> m_AdaptivePAInterpolators; ///< Map between Interpolator objects and tool ID's
const PrintConfig &m_config; ///< Reference to the print configuration.
double m_last_predicted_pa; ///< Last predicted pressure advance value.
double m_max_next_feedrate; ///< Maximum feed rate (speed) for the upcomming island. If no speed is found, the previous island speed is used.
double m_next_feedrate; ///< First feed rate (speed) for the upcomming island.
double m_current_feedrate; ///< Current, latest feedrate.
int m_last_config_index; ///< Filament config index of the last PA_CHANGE tag.
int m_last_extruder_id; ///< Last used extruder ID.
std::regex m_pa_change_pattern; ///< Regular expression to detect PA_CHANGE pattern.
std::regex m_g1_f_pattern; ///< Regular expression to detect G1 F pattern.
std::smatch m_match; ///< Match results for regular expressions.
/**
* @brief Get the PA interpolator attached to the specified filament config index.
* @brief Get the PA interpolator attached to the specified tool ID.
*
* @param config_index The filament config index (one per filament and extruder variant) for which the PA interpolation model is to be returned.
* @return The Adaptive PA Interpolator object corresponding to that index, or nullptr when adaptive PA is off for it.
* This method manually sets the adaptive PA internally held value.
* Call this when changing tools or in any other case where the internally assumed last PA value may be incorrect
*
* @param An integer with the tool ID for which the PA interpolation model is to be returned.
* @return The Adaptive PA Interpolator object corresponding to that tool.
*/
AdaptivePAInterpolator* getInterpolator(unsigned int config_index);
AdaptivePAInterpolator* getInterpolator(unsigned int tool_id);
};
} // namespace Slic3r
-16
View File
@@ -13,19 +13,8 @@
namespace Slic3r {
// How many setters this thread holds, so the ones nested in another can skip
// setlocale, which takes a lock the whole process shares on Windows.
static thread_local int s_numeric_locale_depth = 0;
CNumericLocalesSetter::CNumericLocalesSetter()
{
// Nested in another setter on this thread, whose "C" the separator check
// confirms is still set.
if (s_numeric_locale_depth > 0 && is_decimal_separator_point()) {
m_nested = true;
++ s_numeric_locale_depth;
return;
}
#ifdef _WIN32
_configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
m_orig_numeric_locale = std::setlocale(LC_NUMERIC, nullptr);
@@ -40,17 +29,12 @@ CNumericLocalesSetter::CNumericLocalesSetter()
m_new_locale = newlocale(LC_NUMERIC_MASK, "C", m_new_locale);
uselocale(m_new_locale);
#endif
// Counted last, since the destructor does not run for a constructor that throws.
++ s_numeric_locale_depth;
}
CNumericLocalesSetter::~CNumericLocalesSetter()
{
-- s_numeric_locale_depth;
if (m_nested)
return;
#ifdef _WIN32
std::setlocale(LC_NUMERIC, m_orig_numeric_locale.data());
#else
-5
View File
@@ -19,13 +19,8 @@ class CNumericLocalesSetter {
public:
CNumericLocalesSetter();
~CNumericLocalesSetter();
// A copy would restore the locale twice, and count down once more than up.
CNumericLocalesSetter(const CNumericLocalesSetter&) = delete;
CNumericLocalesSetter& operator=(const CNumericLocalesSetter&) = delete;
private:
// Inside another setter on this thread, which does the setting and restoring.
bool m_nested { false };
#ifdef _WIN32
std::string m_orig_numeric_locale;
#else
-65
View File
@@ -1,65 +0,0 @@
#pragma once
#include <algorithm>
#include <cstddef>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
#include <tbb/blocked_range.h>
#include <tbb/parallel_for.h>
#include <tbb/task_arena.h>
#include <tbb/task_group.h>
#include "Exception.hpp"
namespace Slic3r {
// A batch of resolved presets, each a whole config, is what resolve_then_commit adds
// to peak memory, so it stays far below a vendor's preset count and above any core count.
inline constexpr size_t resolve_batch_size = 64;
// Resolve `count` items that do not depend on each other and install them one at
// a time.
//
// `resolve(i)` runs on any thread and must read only, since the items are
// resolved side by side. `commit(i, resolved)` is called for every item in index
// order, on the calling thread, and is where shared state is written.
//
// The items are worked through in batches, so what is resolved and held at once
// does not grow with `count`. An exception from either callable propagates after
// the batches before it have been committed. A cancellation of the caller's task
// group, which stops a batch partway without an exception, throws RuntimeError
// before that batch commits.
//
// `ChunkSetup`, when given, is constructed once for each piece of a batch TBB hands
// out, for per-thread state a resolve would otherwise set up per item, such as the
// C numeric locale, whose setting takes a lock the whole process shares.
template<class ChunkSetup = std::monostate, class Resolve, class Commit>
void resolve_then_commit(size_t count, Resolve resolve, Commit commit)
{
using Resolved = std::invoke_result_t<Resolve&, size_t>;
std::vector<Resolved> resolved(std::min(count, resolve_batch_size));
for (size_t first = 0; first < count; first += resolve_batch_size) {
const size_t last = std::min(first + resolve_batch_size, count);
// Isolated, so a thread waiting on the batch runs none of the caller's other
// tasks before finishing it.
tbb::this_task_arena::isolate([&] {
tbb::parallel_for(tbb::blocked_range<size_t>(first, last),
[&](const tbb::blocked_range<size_t>& range) {
ChunkSetup setup;
(void) setup;
for (size_t i = range.begin(); i < range.end(); ++ i)
resolved[i - first] = resolve(i);
});
});
if (tbb::is_current_task_group_canceling())
throw RuntimeError("resolve_then_commit: canceled before the batch was resolved");
for (size_t i = first; i < last; ++ i)
commit(i, std::move(resolved[i - first]));
}
}
} // namespace Slic3r
+187 -286
View File
@@ -5,8 +5,6 @@
#include "Preset.hpp"
#include "PresetBundle.hpp"
#include "AppConfig.hpp"
#include "LocalesUtils.hpp"
#include "ParallelResolve.hpp"
#ifdef _MSC_VER
#define WIN32_LEAN_AND_MEAN
@@ -508,11 +506,10 @@ void Preset::normalize(DynamicPrintConfig &config)
handle_legacy_sla(config);
}
std::string Preset::remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config,
const DynamicPrintConfig *added)
std::string Preset::remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config)
{
std::string incorrect_keys;
for (const std::string &key : (added != nullptr ? *added : config).keys())
for (const std::string &key : config.keys())
if (! default_config.has(key)) {
if (incorrect_keys.empty())
incorrect_keys = key;
@@ -624,6 +621,7 @@ void Preset::load_info(const std::string& file)
}
else if (v.first.compare("base_id") == 0) {
this->base_id = v.second.get_value<std::string>();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load info from: " << file << " and base_id: " << this->base_id;
if (this->base_id.compare("null") == 0)
this->base_id.clear();
}
@@ -976,37 +974,6 @@ void Preset::get_extruder_names_and_keysets(Type type, std::string& extruder_id_
}
}
DynamicPrintConfig Preset::load_external_config(Type type, const DynamicPrintConfig &default_config, const DynamicPrintConfig &project_config,
const std::set<std::string> &different_settings_list,
const std::function<DynamicPrintConfig *(const std::string &inherits)> &find_base,
t_config_option_keys *keys)
{
// Load the preset over a default preset, so that the missing fields are filled in from the default preset.
DynamicPrintConfig cfg(default_config);
// SoftFever: ignore print connection info from project
auto cfg_keys = cfg.keys();
cfg_keys.erase(std::remove_if(cfg_keys.begin(), cfg_keys.end(),
[](std::string &val) {
return val == "print_host" || val == "print_host_webui" || val == "printhost_apikey" ||
val == "printhost_cafile" || val == "printhost_user" || val == "printhost_password" || val == "printhost_port";
}),
cfg_keys.end());
cfg.apply_only(project_config, cfg_keys, true);
//add different settings check logic, replace the old system preset's default value with new system preset's default values
if (!different_settings_list.empty()) {
if (DynamicPrintConfig *base_config = find_base(Preset::inherits(cfg))) {
std::string extruder_id_name, extruder_variant_name;
std::set<std::string> *key_set1 = nullptr, *key_set2 = nullptr;
Preset::get_extruder_names_and_keysets(type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2);
cfg.update_non_diff_values_to_base_config(*base_config, cfg_keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
}
}
if (keys)
*keys = std::move(cfg_keys);
return cfg;
}
bool Preset::has_lidar(PresetBundle *preset_bundle)
{
bool has_lidar = false;
@@ -1702,169 +1669,6 @@ std::string PresetCollection::canonical_preset_name(const std::string &name, con
return get_preset_canonical_name(parsed.bare, origin);
}
PresetCollection::UserPresetLoad PresetCollection::resolve_user_preset(
const boost::filesystem::path &file, const std::string &canonical_name,
const PresetOrigin &load_origin, ForwardCompatibilitySubstitutionRule substitution_rule,
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
{
UserPresetLoad out;
out.preset = Preset(m_type, canonical_name, false);
Preset &preset = out.preset;
preset.bundle_id = load_origin.bundle_id;
preset.file = file.string();
// Load the preset file, apply preset values on top of defaults.
try {
fs::path idx_path(preset.file);
idx_path.replace_extension(".info");
if (fs::exists(idx_path)) {
out.info_file = idx_path.string();
preset.load_info(out.info_file);
}
DynamicPrintConfig config;
//BBS: change to json format
//ConfigSubstitutions config_substitutions = config.load_from_ini(preset.file, substitution_rule);
std::map<std::string, std::string> key_values;
std::string reason;
ConfigSubstitutions config_substitutions = config.load_from_json(preset.file, substitution_rule, key_values, reason);
if (! config_substitutions.empty())
out.substitutions.push_back({ preset.name, m_type, PresetConfigSubstitutions::Source::UserFile, preset.file, std::move(config_substitutions) });
if (!reason.empty()) {
out.discard_file = true;
out.errors.push_back((boost::format("parse config %1% failed") % preset.file).str());
return out;
}
std::string version_str = key_values[BBL_JSON_KEY_VERSION];
boost::optional<Semver> version = Semver::parse(version_str);
if (!version) return out;
preset.version = *version;
if (key_values.find(BBL_JSON_KEY_FILAMENT_ID) != key_values.end())
preset.filament_id = key_values[BBL_JSON_KEY_FILAMENT_ID];
if (key_values.find(BBL_JSON_KEY_DESCRIPTION) != key_values.end())
preset.description = key_values[BBL_JSON_KEY_DESCRIPTION];
if (key_values.find(BBL_JSON_KEY_INSTANTIATION) != key_values.end())
preset.is_visible = key_values[BBL_JSON_KEY_INSTANTIATION] != "false";
//Orca: find and use the inherit config as the base
const Preset* inherit_preset = nullptr;
ConfigOption* inherits_config = config.option(BBL_JSON_KEY_INHERITS);
// check inherits_config
if (inherits_config) {
ConfigOptionString * option_str = dynamic_cast<ConfigOptionString *> (inherits_config);
std::string inherits_value = option_str->value;
// Orca: try to find if the parent preset has been renamed
inherit_preset = this->find_preset2(inherits_value);
Preset::normalize_inherits(config, inherit_preset);
}
const Preset& default_preset = this->default_preset_for(config);
if (inherit_preset) {
preset.config = inherit_preset->config;
preset.filament_id = inherit_preset->filament_id;
extend_default_config_length(config, false, {});
preset.config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
}
else {
auto inherits_config2 = dynamic_cast<ConfigOptionString *>(inherits_config);
if ((inherits_config2 && !inherits_config2->value.empty())) {
out.errors.push_back((boost::format("can not find parent %1% for config %2%!") % inherits_config2->value % preset.file).str());
return out;
}
// We support custom root preset now
// Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field.
preset.config = default_preset.config;
preset.config.apply(std::move(config));
extend_default_config_length(preset.config, true, default_preset.config);
}
Preset::normalize(preset.config);
// Report configuration fields, which are misplaced into a wrong group.
std::string incorrect_keys = Preset::remove_invalid_keys(preset.config, default_preset.config);
if (!incorrect_keys.empty())
out.errors.push_back("Error in a preset file: The preset \"" + preset.file +
"\" contains the following incorrect keys: " + incorrect_keys + ", which were removed");
if (preset.type == Preset::TYPE_FILAMENT && preset.is_user() && preset.inherits().empty()) {
auto compatible_printers = dynamic_cast<ConfigOptionStrings *>(preset.config.option("compatible_printers", true));
if (compatible_printers && compatible_printers->values.empty()) {
size_t at_pos = canonical_name.find('@');
if (at_pos != std::string::npos && at_pos + 1 < canonical_name.length()) {
compatible_printers->values.push_back(canonical_name.substr(at_pos + 1));
out.save_compatible_printers = true;
}
}
}
preset.loaded = true;
out.complete = true;
} catch (const std::ifstream::failure &err) {
out.discard_file = true;
out.errors.push_back((boost::format("The user-config cannot be loaded: %1%. Reason: %2%") % preset.file % err.what()).str());
//throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what());
} catch (const std::runtime_error &err) {
out.discard_file = true;
out.errors.push_back((boost::format("Failed loading the user-config file: %1%. Reason: %2%") % preset.file % err.what()).str());
//throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what());
}
out.install = true;
return out;
}
void PresetCollection::commit_user_preset(UserPresetLoad &&loaded, std::deque<Preset> &presets_loaded,
PresetsConfigSubstitutions &substitutions,
const std::function<void(Preset&)> &preset_loaded_fn,
bool read_only)
{
Preset &preset = loaded.preset;
if (! loaded.info_file.empty())
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load info from: " << loaded.info_file << " and base_id: " << preset.base_id;
append(substitutions, std::move(loaded.substitutions));
for (const std::string &error : loaded.errors) {
++m_errors;
BOOST_LOG_TRIVIAL(error) << error;
}
if (loaded.discard_file && !read_only) {
fs::path file_path(loaded.preset.file);
if (fs::exists(file_path))
fs::remove(file_path);
file_path.replace_extension(".info");
if (fs::exists(file_path))
fs::remove(file_path);
}
if (!loaded.install)
return;
if (loaded.complete) {
if (loaded.save_compatible_printers) {
// A filesystem error from the rewrite is counted, and the preset still loads.
try {
if (!read_only)
preset.save(nullptr);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " added compatible_printers for preset: " << preset.name;
} catch (const std::runtime_error &err) {
++m_errors;
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " can not write compatible_printers back to " << preset.file << ": " << err.what();
}
}
//BBS: add some workaround for previous incorrect settings
if ((!preset.setting_id.empty())&&(preset.setting_id == preset.base_id))
preset.setting_id.clear();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load preset: " << preset.name << " and filament_id: " << preset.filament_id << " and base_id: " << preset.base_id;
//BBS: add config related logs
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", preset type %1%, name %2%, path %3%, is_system %4%, is_default %5% is_visible %6%")%Preset::get_type_string(m_type) %preset.name %preset.file %preset.is_system %preset.is_default %preset.is_visible;
// add alias for custom filament preset
set_custom_preset_alias(preset);
}
if (preset_loaded_fn != nullptr)
preset_loaded_fn(preset);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " load config successful and preset name is:" << preset.name;
presets_loaded.emplace_back(std::move(preset));
}
// Load all presets found in dir_path.
// Throws an exception on error.
void PresetCollection::load_presets(
@@ -1902,8 +1706,6 @@ void PresetCollection::load_presets(
std::set<std::string> *key_set1 = nullptr, *key_set2 = nullptr;
Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2);
struct UserPresetFile { fs::path path; std::string canonical_name; };
std::vector<UserPresetFile> files;
//BBS: change to json format
for (auto &dir_entry : boost::filesystem::directory_iterator(dir))
{
@@ -1919,26 +1721,149 @@ void PresetCollection::load_presets(
BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << canonical_name;
continue;
}
files.push_back({ dir_entry.path(), std::move(canonical_name) });
}
}
resolve_then_commit<CNumericLocalesSetter>(files.size(),
[&](size_t i) {
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);
},
[&](size_t, UserPresetLoad &&loaded) {
// Committing can remove an unreadable preset's file, and a filesystem error
// there is reported without stopping the rest of the directory.
try {
this->commit_user_preset(std::move(loaded), presets_loaded, substitutions, preset_loaded_fn, read_only);
Preset preset(m_type, canonical_name, false);
preset.bundle_id = resolved_origin.bundle_id;
preset.file = dir_entry.path().string();
// Load the preset file, apply preset values on top of defaults.
try {
fs::path idx_path(preset.file);
idx_path.replace_extension(".info");
if (fs::exists(idx_path)) {
preset.load_info(idx_path.string());
}
DynamicPrintConfig config;
//BBS: change to json format
//ConfigSubstitutions config_substitutions = config.load_from_ini(preset.file, substitution_rule);
std::map<std::string, std::string> key_values;
std::string reason;
ConfigSubstitutions config_substitutions = config.load_from_json(preset.file, substitution_rule, key_values, reason);
if (! config_substitutions.empty())
substitutions.push_back({ preset.name, m_type, PresetConfigSubstitutions::Source::UserFile, preset.file, std::move(config_substitutions) });
if (!reason.empty()) {
fs::path file_path(preset.file);
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
file_path.replace_extension(".info");
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
BOOST_LOG_TRIVIAL(error) << boost::format("parse config %1% failed")%preset.file;
++m_errors;
continue;
}
std::string version_str = key_values[BBL_JSON_KEY_VERSION];
boost::optional<Semver> version = Semver::parse(version_str);
if (!version) continue;
preset.version = *version;
if (key_values.find(BBL_JSON_KEY_FILAMENT_ID) != key_values.end())
preset.filament_id = key_values[BBL_JSON_KEY_FILAMENT_ID];
if (key_values.find(BBL_JSON_KEY_DESCRIPTION) != key_values.end())
preset.description = key_values[BBL_JSON_KEY_DESCRIPTION];
if (key_values.find(BBL_JSON_KEY_INSTANTIATION) != key_values.end())
preset.is_visible = key_values[BBL_JSON_KEY_INSTANTIATION] != "false";
//Orca: find and use the inherit config as the base
Preset* inherit_preset = nullptr;
ConfigOption* inherits_config = config.option(BBL_JSON_KEY_INHERITS);
// check inherits_config
if (inherits_config) {
ConfigOptionString * option_str = dynamic_cast<ConfigOptionString *> (inherits_config);
std::string inherits_value = option_str->value;
// Orca: try to find if the parent preset has been renamed
inherit_preset = this->find_preset2(inherits_value);
Preset::normalize_inherits(config, inherit_preset);
} else {
;
}
const Preset& default_preset = this->default_preset_for(config);
if (inherit_preset) {
preset.config = inherit_preset->config;
preset.filament_id = inherit_preset->filament_id;
extend_default_config_length(config, false, {});
preset.config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
}
else {
auto inherits_config2 = dynamic_cast<ConfigOptionString *>(inherits_config);
if ((inherits_config2 && !inherits_config2->value.empty())) {
BOOST_LOG_TRIVIAL(error) << boost::format("can not find parent %1% for config %2%!")%inherits_config2->value %preset.file;
++m_errors;
continue;
}
// We support custom root preset now
// Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field.
preset.config = default_preset.config;
preset.config.apply(std::move(config));
extend_default_config_length(preset.config, true, default_preset.config);
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load preset: " << name << " and filament_id: " << preset.filament_id << " and base_id: " << preset.base_id;
Preset::normalize(preset.config);
// Report configuration fields, which are misplaced into a wrong group.
std::string incorrect_keys = Preset::remove_invalid_keys(preset.config, default_preset.config);
if (!incorrect_keys.empty()) {
++m_errors;
BOOST_LOG_TRIVIAL(error)
<< "Error in a preset file: The preset \"" << preset.file
<< "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed";
}
if (preset.type == Preset::TYPE_FILAMENT && preset.is_user() && preset.inherits().empty()) {
auto compatible_printers = dynamic_cast<ConfigOptionStrings *>(preset.config.option("compatible_printers", true));
if (compatible_printers && compatible_printers->values.empty()) {
size_t at_pos = name.find('@');
if (at_pos != std::string::npos && at_pos + 1 < name.length()) {
compatible_printers->values.push_back(name.substr(at_pos + 1));
if (!read_only)
preset.save(nullptr);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " added compatible_printers for preset: " << name;
}
}
}
preset.loaded = true;
//BBS: add some workaround for previous incorrect settings
if ((!preset.setting_id.empty())&&(preset.setting_id == preset.base_id))
preset.setting_id.clear();
//BBS: add config related logs
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", preset type %1%, name %2%, path %3%, is_system %4%, is_default %5% is_visible %6%")%Preset::get_type_string(m_type) %preset.name %preset.file %preset.is_system %preset.is_default %preset.is_visible;
// add alias for custom filament preset
set_custom_preset_alias(preset);
} catch (const std::ifstream::failure &err) {
++m_errors;
BOOST_LOG_TRIVIAL(error) << boost::format("The user-config cannot be loaded: %1%. Reason: %2%")%preset.file %err.what();
fs::path file_path(preset.file);
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
file_path.replace_extension(".info");
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
//throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what());
} catch (const std::runtime_error &err) {
++m_errors;
BOOST_LOG_TRIVIAL(error) << boost::format("Failed loading the user-config file: %1%. Reason: %2%")%preset.file %err.what();
//throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what());
fs::path file_path(preset.file);
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
file_path.replace_extension(".info");
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
}
if (preset_loaded_fn != nullptr)
preset_loaded_fn(preset);
presets_loaded.emplace_back(preset);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " load config successful and preset name is:" << preset.name;
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
errors_cummulative += "\n";
}
});
}
}
if (presets_loaded.size() > 0)
m_presets.insert(m_presets.end(), std::make_move_iterator(presets_loaded.begin()), std::make_move_iterator(presets_loaded.end()));
sort_presets();
@@ -2671,6 +2596,20 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
const Semver file_version,
const std::string filament_id)
{
// Load the preset over a default preset, so that the missing fields are filled in from the default preset.
DynamicPrintConfig cfg(this->default_preset_for(combined_config).config);
// SoftFever: ignore print connection info from project
auto keys = cfg.keys();
keys.erase(std::remove_if(keys.begin(), keys.end(),
[](std::string &val) {
return val == "print_host" || val == "print_host_webui" || val == "printhost_apikey" ||
val == "printhost_cafile" || val == "printhost_user" || val == "printhost_password" || val == "printhost_port";
}),
keys.end());
cfg.apply_only(combined_config, keys, true);
std::string &inherits = Preset::inherits(cfg);
//add different settings check logic, replace the old system preset's default value with new system preset's default values
std::deque<Preset>::iterator it = this->find_preset_internal(original_name);
bool found = it != m_presets.end() && it->name == original_name;
if (! found) {
@@ -2679,25 +2618,24 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
found = it != m_presets.end();
}
t_config_option_keys keys;
DynamicPrintConfig cfg = Preset::load_external_config(m_type, this->default_preset_for(combined_config).config, combined_config, different_settings_list,
[this, &original_name, &path, found, it](const std::string &inherits) -> DynamicPrintConfig * {
if (!inherits.empty()) {
auto iter = this->find_preset_internal(inherits);
if (iter == m_presets.end() || iter->name != inherits)
iter = this->find_preset_renamed(inherits);
if (iter == m_presets.end())
return nullptr;
BOOST_LOG_TRIVIAL(info) << "load_external_preset" << boost::format(": change preset %1% inherit %2% 's value to %3% 's values")%original_name %inherits %path;
return &iter->config;
}
if (found && it->is_system) {
BOOST_LOG_TRIVIAL(info) << "load_external_preset" << boost::format(": change preset %1% 's value to %2% 's values")%original_name %path;
return &it->config;
}
return nullptr;
}, &keys);
std::string &inherits = Preset::inherits(cfg);
std::string extruder_id_name, extruder_variant_name;
std::set<std::string> *key_set1 = nullptr, *key_set2 = nullptr;
Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2);
if (!inherits.empty() && (different_settings_list.size() > 0)) {
auto iter = this->find_preset_internal(inherits);
if (iter == m_presets.end() || iter->name != inherits)
iter = this->find_preset_renamed(inherits);
if (iter != m_presets.end()) {
//std::vector<std::string> dirty_options = cfg.diff(iter->config);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": change preset %1% inherit %2% 's value to %3% 's values")%original_name %inherits %path;
cfg.update_non_diff_values_to_base_config(iter->config, keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
}
}
else if (found && it->is_system && (different_settings_list.size() > 0)) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": change preset %1% 's value to %2% 's values")%original_name %path;
cfg.update_non_diff_values_to_base_config(it->config, keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
}
//BBS: add config related logs
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, type %1% , path %2%, name %3%, original_name %4%, inherits %5%")%Preset::get_type_string(m_type) %path %name %original_name %inherits;
@@ -2889,18 +2827,6 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
return std::make_pair(&preset, false);
}
Preset& PresetCollection::append_preset(std::string &&path, const std::string &name, DynamicPrintConfig &&config)
{
lock();
Preset &preset = m_presets.emplace_back(m_type, name, false);
preset.file = std::move(path);
preset.config = std::move(config);
preset.loaded = true;
preset.is_dirty = false;
unlock();
return preset;
}
Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select, Semver file_version)
{
lock();
@@ -3920,53 +3846,28 @@ bool PresetCollection::select_preset_by_name_strict(const std::string &name)
return false;
}
std::vector<std::vector<std::string>> PresetCollection::merge_presets(const std::vector<PresetCollection*> &others, const VendorMap &new_vendors)
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> PresetCollection::merge_presets(PresetCollection &&other, const VendorMap &new_vendors)
{
auto less = [this](const Preset &a, const Preset &b) {
return m_type == Preset::TYPE_FILAMENT ? filament_preset_less(a, b) : a < b;
};
struct Incoming { Preset *preset; size_t source; };
auto incoming_less = [&less](const Incoming &a, const Incoming &b) { return less(*a.preset, *b.preset); };
// Each of `others` is sorted, so its presets form one sorted run.
std::vector<Incoming> incoming;
std::vector<size_t> run_ends { 0 };
for (size_t source = 0; source < others.size(); ++ source) {
for (Preset &preset : others[source]->m_presets)
if (! preset.is_default && ! preset.is_external)
incoming.push_back({ &preset, source });
assert(std::is_sorted(incoming.begin() + run_ends.back(), incoming.end(), incoming_less));
run_ends.push_back(incoming.size());
}
// Merged pairwise and stably, so equal names stay in the order of `others`.
const size_t runs = others.size();
for (size_t width = 1; width < runs; width *= 2)
for (size_t i = 0; i + width < runs; i += 2 * width)
std::inplace_merge(incoming.begin() + run_ends[i], incoming.begin() + run_ends[i + width],
incoming.begin() + run_ends[std::min(i + 2 * width, runs)], incoming_less);
std::vector<std::vector<std::string>> duplicates(others.size());
std::deque<Preset> merged;
auto own = m_presets.begin() + m_num_default_presets;
std::move(m_presets.begin(), own, std::back_inserter(merged));
// On equal names this collection's preset is kept, else the earliest of `others`,
// and each repeat is listed under the collection it came from.
for (auto next = incoming.begin(); own != m_presets.end() || next != incoming.end();) {
if (next == incoming.end() || (own != m_presets.end() && ! less(*next->preset, *own)))
merged.emplace_back(std::move(*own ++));
else {
Preset &preset = *(next ++)->preset;
std::vector<std::string> duplicates;
for (Preset &preset : other.m_presets) {
if (preset.is_default || preset.is_external)
continue;
Preset key(m_type, preset.name);
auto it = (m_type == Preset::TYPE_FILAMENT)
? std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key, filament_preset_less)
: std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key);
if (it == m_presets.end() || it->name != preset.name) {
if (preset.vendor != nullptr) {
// Re-assign a pointer to the vendor structure in the new PresetBundle.
auto it = new_vendors.find(preset.vendor->id);
assert(it != new_vendors.end());
preset.vendor = &it->second;
}
merged.emplace_back(std::move(preset));
}
for (; next != incoming.end() && next->preset->name == merged.back().name; ++ next)
duplicates[next->source].emplace_back(next->preset->name);
m_presets.emplace(it, std::move(preset));
} else
duplicates.emplace_back(std::move(preset.name));
}
m_presets = std::move(merged);
return duplicates;
}
+3 -60
View File
@@ -403,15 +403,6 @@ public:
std::string get_current_printer_type(PresetBundle *preset_bundle); // get current preset type
static void get_extruder_names_and_keysets(Type type, std::string& extruder_id_name, std::string& extruder_variant_name, std::set<std::string>** p_key_set1, std::set<std::string>** p_key_set2);
// Config of a preset loaded from a project or config file: the project's values over the type's
// default preset config, without the print-host keys. When different_settings_list is not empty,
// every key not listed in it is then refreshed from the base system preset, which find_base returns
// for the project's "inherits" (nullptr when there is none), with the listed per-variant values
// mapped onto the base's extruder variants. keys, if given, receives the keys taken from the project.
static DynamicPrintConfig load_external_config(Type type, const DynamicPrintConfig &default_config, const DynamicPrintConfig &project_config,
const std::set<std::string> &different_settings_list,
const std::function<DynamicPrintConfig *(const std::string &inherits)> &find_base,
t_config_option_keys *keys = nullptr);
std::string get_printer_id() const { return vendor ? vendor->id : ""; }
bool has_lidar(PresetBundle *preset_bundle);
@@ -454,10 +445,7 @@ public:
static std::string remove_suffix_modified(const std::string& name);
static void normalize(DynamicPrintConfig &config);
// Report configuration fields, which are misplaced into a wrong group, remove them from the config.
// `added`, when given, is the diff applied over a copy of default_config, and only
// its keys are checked, since no other key can be missing from default_config.
static std::string remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config,
const DynamicPrintConfig *added = nullptr);
static std::string remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config);
// BBS: move constructor to public
Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {}
@@ -813,8 +801,6 @@ public:
// Return number of presets including the "- default -" preset.
size_t size() const { return m_presets.size(); }
bool has_defaults_only() const { return m_presets.size() <= m_num_default_presets; }
// How many presets this collection refused or repaired while loading.
int error_count() const { return m_errors; }
// For Print / Filament presets, disable those, which are not compatible with the printer.
template<typename PreferedCondition>
@@ -888,10 +874,8 @@ protected:
// This is a temporary state, which shall be fixed immediately by the following step.
bool select_preset_by_name_strict(const std::string &name);
// Move the presets of `others` into this collection in one pass. A name this
// collection or an earlier one of `others` already has is left out, and reported
// in the list of the collection that repeats it.
std::vector<std::vector<std::string>> merge_presets(const std::vector<PresetCollection*> &others, const VendorMap &new_vendors);
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> merge_presets(PresetCollection &&other, const VendorMap &new_vendors);
// Update m_map_alias_to_profile_name from loaded system profiles.
void update_map_alias_to_profile_name();
@@ -906,43 +890,6 @@ protected:
void set_custom_preset_alias(Preset &preset);
private:
// One preset file read and flattened against the presets already in this
// collection, before anything the collection shares has been touched.
struct UserPresetLoad
{
Preset preset;
// Joins the collection. A file that threw partway still joins it, without
// the steps that did not run.
bool install { false };
// The whole of the load ran, so the preset is ready to be aliased.
bool complete { false };
// A filament preset that named no compatible printer and was given one from
// its name, which commit writes back to its file.
bool save_compatible_printers { false };
// Unreadable, so commit removes it and its .info file.
bool discard_file { false };
// The .info file read beside the preset, which commit logs.
std::string info_file;
// Counted and logged by commit, in the order the directory listed the files.
std::vector<std::string> errors;
PresetsConfigSubstitutions substitutions;
};
// Read and flatten one preset file. It reads only, and resolves against the presets
// loaded before this pass, never another file of the same pass, so the files of a
// pass are independent of each other.
UserPresetLoad resolve_user_preset(const boost::filesystem::path &file, const std::string &canonical_name,
const PresetOrigin &load_origin, ForwardCompatibilitySubstitutionRule substitution_rule,
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;
// Install one resolved preset. The collection, its alias maps, the error count
// and the preset files on disk are touched here and only here.
void commit_user_preset(UserPresetLoad &&loaded, std::deque<Preset> &presets_loaded,
PresetsConfigSubstitutions &substitutions,
const std::function<void(Preset&)> &preset_loaded_fn,
bool read_only);
std::string canonical_preset_name(const std::string &name, const PresetOrigin &load_origin = PresetOrigin()) const;
// Comparator that sorts "Generic " prefixed presets before others, then alphabetically within each group.
@@ -954,10 +901,6 @@ private:
return a.name < b.name;
}
// Append a preset without keeping the collection sorted, for a caller installing
// many at once; find_preset() is unusable until sort_presets() runs.
Preset& append_preset(std::string &&path, const std::string &name, DynamicPrintConfig &&config);
// Sort presets: filament presets use generic-first ordering, others sort alphabetically.
void sort_presets() {
if (m_type == Preset::TYPE_FILAMENT)
File diff suppressed because it is too large Load Diff
+29 -182
View File
@@ -15,7 +15,6 @@
#include <unordered_map>
#include <optional>
#include <array>
#include <atomic>
#include <boost/filesystem/path.hpp>
#include <unordered_set>
@@ -235,9 +234,6 @@ public:
// ORCA: utility function to find the vendor for a given preset name
static std::string find_preset_vendor(const std::string& preset_name, Preset::Type type);
// Keys a project keeps when its presets are loaded: those listed in its escaped
// "different_settings_to_system" entry for the preset, plus the preset bookkeeping keys.
static std::set<std::string> project_different_keys(const std::string &different_settings);
PresetBundle();
PresetBundle(const PresetBundle &rhs);
@@ -277,10 +273,6 @@ public:
const std::string &source_file,
ForwardCompatibilitySubstitutionRule compatibility_rule,
std::string &error, bool allow_source_manifest = true);
// Resolve a system preset by name. The vendor tree is read from data_dir()/system when installed
// there, as the GUI reads it, and from the bundled profiles otherwise.
bool resolve_system_preset(DynamicPrintConfig &config, Preset::Type type, const std::string &name,
ForwardCompatibilitySubstitutionRule compatibility_rule, std::string &error);
// Load selections (current print, current filaments, current printer) from config.ini
// This is done just once on application start up.
@@ -630,180 +622,36 @@ public:
// default_filament_profile must resolve to a system filament.
bool check_printer_default_materials() const;
// One vendor to load, and the directory it is installed in.
struct VendorSource
{
std::string name;
boost::filesystem::path dir;
};
// Load `vendors` into this bundle, the Orca filament library directly and every
// other vendor in parallel into a bundle of its own that inherits from it, merged
// in the order given. A vendor that cannot be loaded has its error added to the
// returned text, or thrown in validation mode, and its name to `failed`; it is
// left out, except for the library, which keeps what it installed before the
// failure. Once `cancel` is set, no further vendor starts loading.
std::pair<PresetsConfigSubstitutions, std::string> load_vendors(const std::vector<VendorSource>& vendors,
ForwardCompatibilitySubstitutionRule compatibility_rule, bool allow_cache,
const std::atomic<bool>* cancel = nullptr, std::vector<std::string>* failed = nullptr);
// Merge one vendor's presets with the other vendor's presets, report duplicates.
// Public so per-vendor-cache consumers (e.g. the setup wizard) can assemble a
// bundle out of several per-vendor caches loaded into separate PresetBundle instances.
std::vector<std::string> merge_presets(PresetBundle &&other);
private:
// Move the presets and vendor profiles of `others` into this bundle, in one pass
// over each collection. A preset whose name this bundle or an earlier one of
// `others` already has is left out and listed under the bundle that repeats it.
std::vector<std::vector<std::string>> merge_presets(const std::vector<PresetBundle*> &others);
// Load one vendor from the preset cache installed in `dir`, judged against
// the vendor profile there. False, with this bundle left clean, when there
// is no usable cache and the vendor has to be parsed. This is how
// load_vendor_configs_from_json reads a cache.
bool load_vendor_cache(const boost::filesystem::path& dir, const std::string& vendor_name, const PresetBundle* base_bundle);
// What parsing one entry's JSON sub-file reported. Its errors and warnings are
// logged when the entry installs, so they come out in listing order with the
// entry's install errors, as parsing and installing one entry at a time leaves them.
struct EntryParse
{
ConfigSubstitutions substitutions;
// Counted in the bundle's error count.
std::vector<std::string> errors;
std::vector<std::string> warnings;
};
// An EntryParse for each entry of the VendorCacheData list of the same name.
struct VendorParse
{
std::vector<EntryParse> process_entries;
std::vector<EntryParse> filament_entries;
std::vector<EntryParse> machine_entries;
};
// One vendor read from its cache or its JSONs by read_vendor, for
// install_vendor_read to install.
struct VendorRead
{
std::string dir;
std::string vendor_name;
LoadConfigBundleAttributes flags;
ForwardCompatibilitySubstitutionRule compatibility_rule;
// The errors this bundle had counted before the read, which the cache stamp leaves out.
int errors_at_entry { 0 };
// A whole-vendor load, which can read a cache and write one.
bool cacheable { false };
// Read from the cache at cache_path, which install can still reject.
bool from_cache { false };
std::string cache_path;
// Only the vendor profile was asked for.
bool vendor_only { false };
VendorCacheData data;
// What each entry's JSON parse reported, and whether and with which version
// the cache is written once the entries install.
VendorParse parsed;
bool will_cache { false };
std::string version;
// The sub-file the parse stopped at, its kind, why, and the errors it reported.
std::string reason;
std::string failed_subfile;
const char* failed_kind { nullptr };
std::vector<std::string> failed_errors;
};
// Read a vendor into this bundle's vendor profiles and `data`, from its cache
// when one covers it, else from its JSONs; nothing is installed. Throws
// ConfigurationError when the vendor's own JSON cannot be parsed.
VendorRead read_vendor(const std::string& dir, const std::string& vendor_name, LoadConfigBundleAttributes flags,
ForwardCompatibilitySubstitutionRule compatibility_rule, bool allow_cache);
// Parse the vendor's JSONs into `read`, up to the first sub-file that fails.
void parse_vendor_json(VendorRead& read);
// Install what read_vendor read, against base_bundle's filament library. A cache
// that cannot be installed is replaced by a parse of the JSONs. Throws
// ConfigurationError at the first entry that cannot be installed, or after
// installing the entries before a sub-file that could not be parsed.
std::pair<PresetsConfigSubstitutions, size_t> install_vendor_read(VendorRead&& read, const PresetBundle* base_bundle);
// Install a cache's entries. False, with this bundle left clean, when one of them
// cannot be installed.
bool install_vendor_cache(const std::string& cache_path, const std::string& vendor_name, VendorCacheData&& data,
const PresetBundle* base_bundle);
// Log and count errors reported by a resolve or a parse.
void log_errors(const std::vector<std::string>& errors);
// The state of installing one collection of one vendor, which
// resolve_vendor_preset reads through a const reference and only
// commit_vendor_preset writes.
struct VendorInstall
{
// The directory holding <vendor_name>/, whose sub-paths the entries name.
std::string path;
std::string vendor_name;
const VendorProfile* vendor_profile;
const PresetBundle* base_bundle;
LoadConfigBundleAttributes flags;
PresetCollection* presets;
// The Orca filament library, which keeps every config for other vendors to
// resolve against.
bool is_from_lib;
PresetsConfigSubstitutions* substitutions;
// The names some entry inherits / includes, the only ones whose configs /
// include diffs are looked up again.
std::set<std::string> inherited;
std::set<std::string> included;
std::map<std::string, DynamicPrintConfig> config_maps;
std::map<std::string, DynamicPrintConfig> include_maps;
std::map<std::string, std::string> filament_id_maps;
std::unordered_set<std::string> installed_names;
size_t count { 0 };
};
// Install a vendor's source-form entries, parsed from its JSON or read from its
// cache: processes, then filaments, then printers. Both loads go through here,
// so a cache-loaded bundle cannot come out different from a JSON-loaded one.
// `parsed` is given for entries parsed just now. `complete` says the entries
// are the vendor's whole lists; only then are the filament library's configs
// and filament ids left in m_config_maps and m_filament_id_maps. Returns the
// number of presets installed, and throws ConfigurationError at the first
// entry that cannot be installed.
size_t install_vendor(const std::string& path, const std::string& vendor_name, const PresetBundle* base_bundle,
LoadConfigBundleAttributes flags, const VendorCacheData& entries,
VendorParse* parsed, bool complete, PresetsConfigSubstitutions& substitutions);
// Install one collection's entries in the order they are listed.
void install_vendor_entries(VendorInstall& install, const std::vector<CachedPreset>& entries,
std::vector<EntryParse>* parsed);
// One entry flattened against the preset it inherits, before anything this
// bundle shares has been touched.
struct PresetInstall
{
DynamicPrintConfig config;
std::string file_path;
// Empty when the preset is its own alias.
std::string alias;
std::string filament_id;
std::vector<std::string> renamed_from;
// Reported by commit, so resolving entries together leaves the log and
// the error count as one entry at a time produces them.
std::vector<std::string> errors;
// What a base states for the presets that include it, when it is retained.
std::optional<DynamicPrintConfig> included;
// The config kept for the entries that inherit this one, or for other
// vendors when this is the filament library.
std::optional<DynamicPrintConfig> retained;
// Not instantiated, so it contributes a config and no preset.
bool config_only { false };
// Non-empty when the entry is rejected, and says why.
std::string reason;
};
// Flatten one entry against the config it inherits, from this collection's
// config_maps or base_bundle's filament library, with the include diffs it
// names layered in. It looks up nothing but the names the entry inherits and
// includes, and writes nothing.
PresetInstall resolve_vendor_preset(const CachedPreset& entry, const VendorInstall& install) const;
// Install a resolved entry. The collections, the maps in `install` and the
// error count are touched here and only here, one entry at a time.
// Presets are appended, so a repeated name is caught with `installed_names`,
// and the collection is sorted once every entry is in.
std::string commit_vendor_preset(const CachedPreset& entry, PresetInstall&& resolved,
ConfigSubstitutions&& substitutions, VendorInstall& install);
// Load one source-form preset entry into this bundle: resolve `inherits`
// and `include`, flatten, validate and register the preset. Returns the
// reason loading failed, empty on success. See the definition for the
// sharing contract between the JSON parse and the cache load.
// retain_configs / retain_includes, when non-null, name the only presets
// registered into config_maps / include_maps (a config copy each). The
// cache load passes the names its entries inherit / include — the only
// ones ever looked up again; the JSON parse retains all, not knowing what
// later subfiles name.
std::string load_vendor_preset(const CachedPreset& entry,
const std::string& path, const std::string& vendor_name,
const PresetBundle* base_bundle,
LoadConfigBundleAttributes flags,
ConfigSubstitutionContext& substitution_context, PresetsConfigSubstitutions& substitutions,
std::map<std::string, DynamicPrintConfig>& config_maps, std::map<std::string, DynamicPrintConfig>& include_maps,
std::map<std::string, std::string>& filament_id_maps,
PresetCollection* presets_collection, size_t& count, bool is_from_lib,
const std::set<std::string>* retain_configs = nullptr, const std::set<std::string>* retain_includes = nullptr);
// Clear every collection's m_printer_hold_alias, which reset() leaves alone.
void clear_printer_hold_aliases();
@@ -815,14 +663,13 @@ private:
// Vendor trees loaded by resolve_preset_config's manifest path, so every preset
// resolved through this bundle shares one load per source root and vendor. The
// filament library is one such tree, shared by every vendor under its root.
// A tree read from its preset cache is kept apart: its presets carry no source file.
std::map<std::tuple<std::string, std::string, ForwardCompatibilitySubstitutionRule, bool>, std::unique_ptr<PresetBundle>>
std::map<std::tuple<std::string, std::string, ForwardCompatibilitySubstitutionRule>, std::unique_ptr<PresetBundle>>
m_source_vendor_bundles;
const PresetBundle *load_source_vendor(const boost::filesystem::path &root_dir,
const std::string &vendor_id,
ForwardCompatibilitySubstitutionRule compatibility_rule,
std::string &error, bool allow_cache = false);
std::string &error);
// Orca: validation only - flag any printer with two or more compatible
// filament presets sharing one filament_id (ambiguous AMS subtype match).
+1 -1
View File
@@ -109,7 +109,7 @@ void skip_config(cereal::BinaryInputArchive& ar, const CacheDictionary& dict);
// One preset as its JSON subfile states it: the config diff, the names of the
// preset it inherits and the presets it includes, and the parse metadata —
// everything PresetBundle::parse_vendor_json extracts and
// everything the parse phase of load_vendor_configs_from_json extracts and
// nothing it derives. Inheritance and includes are resolved when the entry is
// installed, against whatever filament library is loaded then, so a cache
// carries no other vendor's values and no other vendor's update can make it
+5 -11
View File
@@ -2439,18 +2439,12 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
if (m_default_region_config.precise_outer_wall && m_default_region_config.wall_sequence != WallSequence::InnerOuter)
warn(L("The precise wall option will be ignored for outer-inner or inner-outer-inner wall sequences."), "precise_outer_wall");
// check adaptive pressure advance model of every extruder variant column of the used filaments
const std::vector<int> &self_index = m_config.filament_self_index.values;
const size_t pa_columns = std::max(m_config.adaptive_pressure_advance_model.size(), size_t(extruders.back()) + 1);
for (size_t column = 0; column < pa_columns; ++column) {
// filament_self_index maps a column to its filament once the filament arrays hold one column per variant
const unsigned int filament_id = self_index.size() == pa_columns ? self_index[column] - 1 : column;
if (!std::binary_search(extruders.begin(), extruders.end(), filament_id))
continue;
if (m_config.adaptive_pressure_advance.get_at(column) &&
m_config.enable_pressure_advance.get_at(column)) {
// check adaptive pressure advance model
for (unsigned int extruder_id : extruders) {
if (m_config.adaptive_pressure_advance.get_at(extruder_id) &&
m_config.enable_pressure_advance.get_at(extruder_id)) {
const std::string pa_model = m_config.adaptive_pressure_advance_model.get_at(column);
const std::string pa_model = m_config.adaptive_pressure_advance_model.get_at(extruder_id);
if (!pa_model.empty()) {
std::string validation_error = AdaptivePAProcessor::validate_adaptive_pa_model(pa_model);
if (!validation_error.empty()) {
-31
View File
@@ -9403,10 +9403,6 @@ void PrintConfigDef::handle_legacy_composite(DynamicPrintConfig &config)
}
config.set_key_value("wiping_volumes_use_custom_matrix", new ConfigOptionBool(custom));
}
// Orca: a config saved before a key joined filament_options_with_variant stores it once per filament
// rather than once per filament variant, and one exported by an older CLI may store a single value.
normalize_filament_values_to_variants(config);
}
const PrintConfigDef print_config_def;
@@ -9509,13 +9505,6 @@ std::set<std::string> filament_options_with_variant = {
"filament_ironing_spacing",
"filament_ironing_inset",
"filament_ironing_speed",
// Orca: pressure advance
"enable_pressure_advance",
"pressure_advance",
"adaptive_pressure_advance",
"adaptive_pressure_advance_model",
"adaptive_pressure_advance_overhangs",
"adaptive_pressure_advance_bridges",
"activate_air_filtration",
"activate_air_filtration_during_print",
"activate_air_filtration_on_completion",
@@ -10723,25 +10712,6 @@ void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVect
target.set_to_index(&source, indices, stride);
}
void normalize_filament_values_to_variants(DynamicPrintConfig &config)
{
const auto *self_index = config.option<ConfigOptionInts>("filament_self_index");
if (self_index == nullptr || self_index->empty())
return;
const int filament_count = *std::max_element(self_index->values.begin(), self_index->values.end());
if (filament_count <= 0 || size_t(filament_count) >= self_index->size())
return;
for (const std::string &key : filament_options_with_variant) {
auto *opt = dynamic_cast<ConfigOptionVectorBase *>(config.option(key));
if (opt == nullptr || (opt->size() != size_t(filament_count) && opt->size() != 1))
continue;
std::unique_ptr<ConfigOption> per_filament(opt->clone());
// set_at() takes the first value for a filament past the end of a single-value vector
for (size_t variant = 0; variant < self_index->size(); ++variant)
opt->set_at(per_filament.get(), variant, self_index->values[variant] - 1);
}
}
//used for object/region config
//use the smallest of multiple to single
@@ -12595,7 +12565,6 @@ OtherSlicingStatesConfigDef::OtherSlicingStatesConfigDef()
new_def("initial_no_support_extruder", coInt, "Initial no support extruder", "Zero-based index of the first extruder used for printing without support. Same as initial_no_support_tool.");
new_def("in_head_wrap_detect_zone", coBool, "In head wrap detect zone", "Indicates if the first layer overlaps with the head wrap zone.");
new_def("curr_bed_type", coString, "Current bed type", "Name of the currently selected bed plate type (e.g. 'Textured PEI Plate', 'Smooth High Temp Plate').");
}
PrintStatisticsConfigDef::PrintStatisticsConfigDef()
-5
View File
@@ -884,11 +884,6 @@ extern std::set<std::string> empty_options;
void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVectorBase &source,
const std::vector<int> &variant_index, int stride = 1);
// Orca: lays every filament_options_with_variant option out one value per filament variant, as
// filament_self_index maps the variants to filaments. An option holding one value per filament, or a
// single value, gives every variant of a filament that filament's value; other lengths are left alone.
void normalize_filament_values_to_variants(DynamicPrintConfig &config);
extern std::set<std::string> filament_dev_options;
extern void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector<int> variant_index, std::set<std::string>& key_set1, int stride = 1);
+1 -10
View File
@@ -256,19 +256,10 @@ extern bool is_gallery_file(const std::string& path, char const* type);
extern bool is_shapes_dir(const std::string& dir);
//BBS: add json support
extern bool is_json_file(const std::string& path);
// True if rel_path is relative, has no ".." component or embedded NUL and, joined to root, still resolves inside it.
// True if rel_path is relative, has no ".." component and, joined to root, still resolves inside it.
// Both '/' and '\\' are treated as separators on every platform, so an archive rejected on one OS
// is rejected on all of them.
extern bool is_path_within_root(const std::string &rel_path, const boost::filesystem::path &root);
// True if a symlink stored at link_rel_path (relative to root) with this target stays inside root: the target
// must be relative, and joined to the link's directory it must pass is_path_within_root.
extern bool is_symlink_target_within_root(const std::string &link_rel_path, const std::string &target, const boost::filesystem::path &root);
// True if path names an entry strictly inside root: it must be spelled with root as its prefix,
// and must still resolve inside root once symlinks are followed.
extern bool is_absolute_path_within_root(const boost::filesystem::path &path, const boost::filesystem::path &root);
// True if a file with this name is of a type that the desktop opens as plain content, so it cannot run code.
// Anything unknown is not safe.
extern bool is_safe_to_open_file_name(const std::string &file_name);
// Orca: custom protocal support utils
inline bool is_orca_open(const std::string& url) { return boost::starts_with(url, "orcaslicer://open"); }
-63
View File
@@ -4,9 +4,6 @@
#include "miniz_extension.hpp"
#include "Utils.hpp"
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#if defined(_MSC_VER) || defined(__MINGW64__)
#include "boost/nowide/cstdio.hpp"
#endif
@@ -118,66 +115,6 @@ std::string decode_archive_entry_path(mz_zip_archive *zip, const mz_zip_archive_
return decode_zip_unicode_path_extra_field(extra.substr(0, extra_size > 0 ? extra_size - 1 : 0), stat.m_filename);
}
bool extract_archive_confined(const std::string &zip_path_utf8, const std::string &dest_dir)
{
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
if (!open_zip_reader(&archive, zip_path_utf8)) {
BOOST_LOG_TRIVIAL(error) << "Unable to open zip reader for " << zip_path_utf8;
return false;
}
const mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
mz_zip_archive_file_stat stat;
// Validate every entry first so an archive with a single escaping entry leaves no partial output behind.
const boost::filesystem::path root(dest_dir);
for (mz_uint i = 0; i < num_entries; ++i) {
if (mz_zip_reader_file_stat(&archive, i, &stat) && !is_path_within_root(stat.m_filename, root)) {
BOOST_LOG_TRIVIAL(error) << "Unzip: rejecting " << zip_path_utf8 << ", entry " << stat.m_filename << " resolves outside " << dest_dir;
close_zip_reader(&archive);
return false;
}
}
for (mz_uint i = 0; i < num_entries; ++i) {
if (!mz_zip_reader_file_stat(&archive, i, &stat)) {
BOOST_LOG_TRIVIAL(warning) << "Unzip: read file stat failed";
continue;
}
const std::string dest_file = dest_dir + "/" + stat.m_filename;
try {
if (stat.m_is_directory) {
const boost::filesystem::path dest_path(dest_file);
if (!boost::filesystem::exists(dest_path))
boost::filesystem::create_directories(dest_path);
continue;
}
if (stat.m_uncomp_size == 0) {
BOOST_LOG_TRIVIAL(warning) << "Unzip: invalid size for file " << stat.m_filename;
continue;
}
// Replace a symlink at the destination rather than writing through it.
const boost::filesystem::path dest_path(dest_file);
if (boost::filesystem::is_symlink(boost::filesystem::symlink_status(dest_path)))
boost::filesystem::remove(dest_path);
if (!mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_file.c_str(), 0)) {
BOOST_LOG_TRIVIAL(error) << "Unzip: extract file " << stat.m_filename << " to dest " << dest_file << " failed";
close_zip_reader(&archive);
return false;
}
BOOST_LOG_TRIVIAL(info) << "Unzip: successfully extract file " << stat.m_file_index << " to " << dest_file;
} catch (const std::exception &e) {
close_zip_reader(&archive);
BOOST_LOG_TRIVIAL(error) << "Unzip: archive read exception: " << e.what();
return false;
}
}
close_zip_reader(&archive);
return true;
}
MZ_Archive::MZ_Archive()
{
mz_zip_zero_struct(&arch);
-2
View File
@@ -11,8 +11,6 @@ bool open_zip_writer(mz_zip_archive *zip, const std::string &fname_utf8);
bool close_zip_reader(mz_zip_archive *zip);
bool close_zip_writer(mz_zip_archive *zip);
std::string decode_archive_entry_path(mz_zip_archive *zip, const mz_zip_archive_file_stat &stat);
// Extracts every entry of the archive under dest_dir. Nothing is written if any entry would resolve outside dest_dir.
bool extract_archive_confined(const std::string &zip_path_utf8, const std::string &dest_dir);
class MZ_Archive {
public:
+1 -39
View File
@@ -70,7 +70,6 @@
#include <boost/shared_ptr.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/nowide/fstream.hpp>
@@ -1094,9 +1093,6 @@ bool is_path_within_root(const std::string &rel_path, const boost::filesystem::p
auto is_separator = [](char c) { return c == '/' || c == '\\'; };
if (rel_path.empty() || is_separator(rel_path.front()) || (rel_path.size() > 1 && rel_path[1] == ':'))
return false;
// The filesystem calls stop at a NUL, so they would act on a shorter path than the one checked here.
if (rel_path.find('\0') != std::string::npos)
return false;
for (size_t start = 0; start <= rel_path.size();) {
size_t end = start;
while (end < rel_path.size() && !is_separator(rel_path[end]))
@@ -1107,10 +1103,7 @@ bool is_path_within_root(const std::string &rel_path, const boost::filesystem::p
}
// Resolve against the canonical root so a symlink inside it cannot lead back out.
try {
std::string root_str = boost::filesystem::weakly_canonical(root).string();
// A trailing separator on root would otherwise fail the prefix match below for every path.
while (!root_str.empty() && (root_str.back() == '/' || root_str.back() == boost::filesystem::path::preferred_separator))
root_str.pop_back();
const std::string root_str = boost::filesystem::weakly_canonical(root).string();
const std::string full_str = boost::filesystem::weakly_canonical(root / rel_path).string();
return full_str.compare(0, root_str.size(), root_str) == 0 &&
(full_str.size() == root_str.size() || full_str[root_str.size()] == boost::filesystem::path::preferred_separator);
@@ -1119,37 +1112,6 @@ bool is_path_within_root(const std::string &rel_path, const boost::filesystem::p
}
}
bool is_symlink_target_within_root(const std::string &link_rel_path, const std::string &target, const boost::filesystem::path &root)
{
if (target.empty() || target.front() == '/' || target.front() == '\\' || (target.size() > 1 && target[1] == ':'))
return false;
// A relative target without ".." only descends from the link's directory, so no chain of such links can leave root.
const size_t sep = link_rel_path.find_last_of("/\\");
return is_path_within_root((sep == std::string::npos ? std::string() : link_rel_path.substr(0, sep + 1)) + target, root);
}
bool is_absolute_path_within_root(const boost::filesystem::path &path, const boost::filesystem::path &root)
{
const boost::filesystem::path rel = path.lexically_relative(root);
return !rel.empty() && rel != "." && is_path_within_root(rel.string(), root);
}
bool is_safe_to_open_file_name(const std::string &file_name)
{
// Formats that cannot carry macros or scripts. Legacy and OpenDocument office files, HTML and SVG are left out on purpose.
static const std::vector<std::string> safe_extensions = {
"jpg", "jpeg", "jfif", "pjpeg", "pjp", "png", "gif", "bmp", "webp", "tif", "tiff",
"pdf", "txt", "md", "csv", "docx", "xlsx", "pptx",
"stl", "obj", "3mf", "amf", "ply", "step", "stp", "iges", "igs", "dxf",
"mp4", "mov", "webm"};
// The name must end in the extension itself: Windows drops trailing dots and spaces and reads ':' as a stream separator.
const size_t dot = file_name.find_last_of('.');
if (dot == std::string::npos || file_name.find_first_of("/\\:") != std::string::npos)
return false;
const std::string extension = boost::algorithm::to_lower_copy(file_name.substr(dot + 1));
return std::find(safe_extensions.begin(), safe_extensions.end(), extension) != safe_extensions.end();
}
bool is_img_file(const std::string &path)
{
return boost::iends_with(path, ".png") || boost::iends_with(path, ".svg");
+15
View File
@@ -158,6 +158,21 @@ enum class EGCodeExtrusionRole : uint8_t
static constexpr std::size_t GCODE_EXTRUSION_ROLES_COUNT = static_cast<std::size_t>(EGCodeExtrusionRole::COUNT);
//
// What the reduced set, drawn while the user is dragging, holds in place of the full toolpaths
//
enum class EReducedDetailMode : uint8_t
{
// nothing: no reduced set is built
Off,
// only the bottom and top layers of the visible range, for a caller that draws the print
// itself some other way
EndLayersOnly,
// every role but the infill under the skins and the gap fill: what can be seen from outside
ShellOnly,
COUNT
};
//
// Option types
//
+13
View File
@@ -114,6 +114,19 @@ public:
//
bool is_dim_previous_layers() const;
void set_dim_previous_layers(bool value);
//
// The reduced set drawn while the user drags: what the mode keeps, and always the bottom and
// top layers of the visible range. While a mode is set it is built alongside the full set, so
// set_reduced_detail() rebuilds nothing. Ignored on the OpenGL ES path.
//
EReducedDetailMode get_reduced_detail_mode() const;
void set_reduced_detail_mode(EReducedDetailMode mode);
// Whether the shell mode hides the infill roles; off for a profile that leaves them on the
// surface, with no top or bottom shell or no walls.
bool get_reduced_detail_hide_infill() const;
void set_reduced_detail_hide_infill(bool value);
void set_reduced_detail(bool value);
bool is_reduced_detail() const;
float get_dim_previous_layers_brightness() const;
void set_dim_previous_layers_brightness(float value);
//
+5
View File
@@ -25,6 +25,11 @@ struct Settings
// ORCA: how bright those darkened layers are rendered, 1.0 = unchanged, 0.0 = black
float dim_previous_layers_brightness{ 0.4f };
bool spiral_vase_mode{ false };
// what the reduced set holds and whether it is drawn. Ignored on the OpenGL ES path.
EReducedDetailMode reduced_detail_mode{ EReducedDetailMode::Off };
// whether the shell mode may hide the infill roles: false when the profile leaves them on the surface
bool reduced_detail_hide_infill{ true };
bool reduced_detail{ false };
//
// Required update flags
//
+30
View File
@@ -92,6 +92,36 @@ bool Viewer::is_dim_previous_layers() const
return m_impl->is_dim_previous_layers();
}
void Viewer::set_reduced_detail(bool value)
{
m_impl->set_reduced_detail(value);
}
bool Viewer::is_reduced_detail() const
{
return m_impl->is_reduced_detail();
}
EReducedDetailMode Viewer::get_reduced_detail_mode() const
{
return m_impl->get_reduced_detail_mode();
}
void Viewer::set_reduced_detail_mode(EReducedDetailMode mode)
{
m_impl->set_reduced_detail_mode(mode);
}
bool Viewer::get_reduced_detail_hide_infill() const
{
return m_impl->get_reduced_detail_hide_infill();
}
void Viewer::set_reduced_detail_hide_infill(bool value)
{
m_impl->set_reduced_detail_hide_infill(value);
}
void Viewer::set_dim_previous_layers(bool value)
{
m_impl->set_dim_previous_layers(value);
+104 -9
View File
@@ -17,6 +17,7 @@
#include <algorithm>
#include <cmath>
#include <numeric>
#include <cfloat>
namespace libvgcode {
@@ -901,9 +902,17 @@ void ViewerImpl::reset()
#else
m_enabled_segments_count = 0;
m_enabled_options_count = 0;
m_enabled_segments_reduced_count = 0;
m_enabled_options_reduced_count = 0;
m_enabled_segments_reduced_tex_size = 0;
m_enabled_options_reduced_tex_size = 0;
m_settings_used_for_ranges = std::nullopt;
delete_textures(m_enabled_options_reduced_tex_id);
delete_buffers(m_enabled_options_reduced_buf_id);
delete_textures(m_enabled_segments_reduced_tex_id);
delete_buffers(m_enabled_segments_reduced_buf_id);
delete_textures(m_enabled_options_tex_id);
delete_buffers(m_enabled_options_buf_id);
delete_textures(m_enabled_segments_tex_id);
@@ -1163,6 +1172,17 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
glsafe(glGenTextures(1, &m_enabled_options_tex_id));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_options_tex_id));
// create (but do not fill) the reduced counterparts of the two buffers above
glsafe(glGenBuffers(1, &m_enabled_segments_reduced_buf_id));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_buf_id));
glsafe(glGenTextures(1, &m_enabled_segments_reduced_tex_id));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_tex_id));
glsafe(glGenBuffers(1, &m_enabled_options_reduced_buf_id));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_options_reduced_buf_id));
glsafe(glGenTextures(1, &m_enabled_options_reduced_tex_id));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_options_reduced_tex_id));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, old_bound_texture));
#endif // ENABLE_OPENGL_ES
@@ -1174,6 +1194,18 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
update_colors();
}
#ifndef ENABLE_OPENGL_ES
// the roles that lie under a skin or between walls, never seen from outside the print
static bool is_hidden_in_shell(EGCodeExtrusionRole role)
{
return role == EGCodeExtrusionRole::InternalInfill ||
role == EGCodeExtrusionRole::SolidInfill ||
role == EGCodeExtrusionRole::InternalBridgeInfill ||
role == EGCodeExtrusionRole::GapFill;
}
#endif // ENABLE_OPENGL_ES
void ViewerImpl::update_enabled_entities()
{
if (m_vertices.empty())
@@ -1181,6 +1213,17 @@ void ViewerImpl::update_enabled_entities()
std::vector<uint32_t> enabled_segments;
std::vector<uint32_t> enabled_options;
#ifndef ENABLE_OPENGL_ES
// the reduced set is filled by the same walk, so switching to it costs no rebuild. Whatever the
// mode leaves out, the bottom and top layers of the visible range are kept whole: they are the
// surfaces the range cuts open
const EReducedDetailMode reduced_mode = m_settings.reduced_detail_mode;
const bool build_reduced = reduced_mode != EReducedDetailMode::Off;
const bool hide_infill = m_settings.reduced_detail_hide_infill;
std::vector<uint32_t> enabled_segments_reduced;
std::vector<uint32_t> enabled_options_reduced;
const Interval& layers_range = m_layers.get_view_range();
#endif // ENABLE_OPENGL_ES
Interval range = m_view_range.get_visible();
// when top layer only visualization is enabled, we need to render
@@ -1228,6 +1271,20 @@ void ViewerImpl::update_enabled_entities()
enabled_options.push_back(static_cast<uint32_t>(i));
else
enabled_segments.push_back(static_cast<uint32_t>(i));
#ifndef ENABLE_OPENGL_ES
if (build_reduced) {
const bool end_layer = v.layer_id == layers_range[0] || v.layer_id == layers_range[1];
if (end_layer)
(v.is_option() ? enabled_options_reduced : enabled_segments_reduced).push_back(static_cast<uint32_t>(i));
else if (reduced_mode == EReducedDetailMode::ShellOnly) {
if (v.is_option())
enabled_options_reduced.push_back(static_cast<uint32_t>(i));
else if (!v.is_extrusion() || !hide_infill || !is_hidden_in_shell(v.role))
enabled_segments_reduced.push_back(static_cast<uint32_t>(i));
}
}
#endif // ENABLE_OPENGL_ES
}
#ifdef ENABLE_OPENGL_ES
@@ -1256,6 +1313,23 @@ void ViewerImpl::update_enabled_entities()
else
glsafe(glBufferData(GL_TEXTURE_BUFFER, 0, nullptr, GL_STATIC_DRAW));
m_enabled_segments_reduced_count = enabled_segments_reduced.size();
m_enabled_options_reduced_count = enabled_options_reduced.size();
m_enabled_segments_reduced_tex_size = enabled_segments_reduced.size() * sizeof(uint32_t);
m_enabled_options_reduced_tex_size = enabled_options_reduced.size() * sizeof(uint32_t);
// uploaded even when nothing was built, so that the last reduced set is released as soon as
// the preference is switched off
assert(m_enabled_segments_reduced_buf_id > 0);
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_buf_id));
glsafe(glBufferData(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_tex_size,
enabled_segments_reduced.empty() ? nullptr : enabled_segments_reduced.data(), GL_STATIC_DRAW));
assert(m_enabled_options_reduced_buf_id > 0);
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_options_reduced_buf_id));
glsafe(glBufferData(GL_TEXTURE_BUFFER, m_enabled_options_reduced_tex_size,
enabled_options_reduced.empty() ? nullptr : enabled_options_reduced.data(), GL_STATIC_DRAW));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0));
#endif // ENABLE_OPENGL_ES
@@ -1463,6 +1537,23 @@ void ViewerImpl::toggle_top_layer_only_view_range()
update_colors_texture();
}
// Both decide which vertices land in the reduced set, so the sets are rebuilt.
void ViewerImpl::set_reduced_detail_mode(EReducedDetailMode mode)
{
if (m_settings.reduced_detail_mode == mode)
return;
m_settings.reduced_detail_mode = mode;
m_settings.update_enabled_entities = true;
}
void ViewerImpl::set_reduced_detail_hide_infill(bool value)
{
if (m_settings.reduced_detail_hide_infill == value)
return;
m_settings.reduced_detail_hide_infill = value;
m_settings.update_enabled_entities = true;
}
// ORCA: enable/disable darkening of the layers the layer slider is not scrubbed to
void ViewerImpl::set_dim_previous_layers(bool value)
{
@@ -1856,6 +1947,8 @@ size_t ViewerImpl::get_used_gpu_memory() const
ret += m_colors_tex_size;
ret += m_enabled_segments_tex_size;
ret += m_enabled_options_tex_size;
ret += m_enabled_segments_reduced_tex_size;
ret += m_enabled_options_reduced_tex_size;
#endif // ENABLE_OPENGL_ES
return ret;
}
@@ -2072,7 +2165,8 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
#ifdef ENABLE_OPENGL_ES
if (m_texture_data.get_enabled_segments_count() == 0)
#else
if (m_enabled_segments_count == 0)
const ActiveSet segments = active_segments();
if (segments.count == 0)
#endif // ENABLE_OPENGL_ES
return;
@@ -2099,7 +2193,7 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
const bool top_down = !m_rendering_shadow_casters && view_matrix[10] > 0.0f;
glsafe(glUniform1i(m_uni_segments_reverse_order_id, top_down ? 1 : 0));
#ifndef ENABLE_OPENGL_ES
glsafe(glUniform1i(m_uni_segments_instance_count_id, static_cast<int>(m_enabled_segments_count)));
glsafe(glUniform1i(m_uni_segments_instance_count_id, static_cast<int>(segments.count)));
#endif // ENABLE_OPENGL_ES
// ORCA: realistic view. The depth pass writes the map it would otherwise read, so it shades
// with the lookup off.
@@ -2149,10 +2243,10 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_colors_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_colors_buf_id));
glsafe(glActiveTexture(GL_TEXTURE3));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_segments_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, m_enabled_segments_buf_id));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, segments.tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, segments.buf_id));
m_segment_template.render(m_enabled_segments_count);
m_segment_template.render(segments.count);
#endif // ENABLE_OPENGL_ES
if (curr_cull_face)
@@ -2178,7 +2272,8 @@ void ViewerImpl::render_options(const Mat4x4& view_matrix, const Mat4x4& project
#ifdef ENABLE_OPENGL_ES
if (m_texture_data.get_enabled_options_count() == 0)
#else
if (m_enabled_options_count == 0)
const ActiveSet options = active_options();
if (options.count == 0)
#endif // ENABLE_OPENGL_ES
return;
@@ -2236,10 +2331,10 @@ void ViewerImpl::render_options(const Mat4x4& view_matrix, const Mat4x4& project
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_colors_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_colors_buf_id));
glsafe(glActiveTexture(GL_TEXTURE3));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_options_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, m_enabled_options_buf_id));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, options.tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, options.buf_id));
m_option_template.render(m_enabled_options_count);
m_option_template.render(options.count);
#endif // ENABLE_OPENGL_ES
if (!curr_cull_face)
+45
View File
@@ -109,6 +109,21 @@ public:
// 0.0 = black
bool is_dim_previous_layers() const { return m_settings.dim_previous_layers; }
void set_dim_previous_layers(bool value);
//
// Draw from the reduced set; it is already built, so this is just a buffer binding.
//
void set_reduced_detail(bool value) {
#ifdef ENABLE_OPENGL_ES
// no reduced set is built on OpenGL ES
value = false;
#endif // ENABLE_OPENGL_ES
m_settings.reduced_detail = value;
}
bool is_reduced_detail() const { return m_settings.reduced_detail; }
EReducedDetailMode get_reduced_detail_mode() const { return m_settings.reduced_detail_mode; }
void set_reduced_detail_mode(EReducedDetailMode mode);
bool get_reduced_detail_hide_infill() const { return m_settings.reduced_detail_hide_infill; }
void set_reduced_detail_hide_infill(bool value);
float get_dim_previous_layers_brightness() const { return m_settings.dim_previous_layers_brightness; }
void set_dim_previous_layers_brightness(float value);
@@ -501,6 +516,15 @@ private:
unsigned int m_enabled_options_tex_id{ 0 };
size_t m_enabled_options_count{ 0 };
//
// OpenGL buffers to store the reduced set drawn while Settings::reduced_detail is set
//
unsigned int m_enabled_segments_reduced_buf_id{ 0 };
unsigned int m_enabled_segments_reduced_tex_id{ 0 };
size_t m_enabled_segments_reduced_count{ 0 };
unsigned int m_enabled_options_reduced_buf_id{ 0 };
unsigned int m_enabled_options_reduced_tex_id{ 0 };
size_t m_enabled_options_reduced_count{ 0 };
//
// Caches for size of data sent to gpu, in bytes
//
size_t m_positions_tex_size{ 0 };
@@ -508,6 +532,27 @@ private:
size_t m_colors_tex_size{ 0 };
size_t m_enabled_segments_tex_size{ 0 };
size_t m_enabled_options_tex_size{ 0 };
size_t m_enabled_segments_reduced_tex_size{ 0 };
size_t m_enabled_options_reduced_tex_size{ 0 };
// The set the next draw reads from: the reduced one while dragging, if one is built.
bool use_reduced_set() const { return m_settings.reduced_detail && m_settings.reduced_detail_mode != EReducedDetailMode::Off; }
struct ActiveSet
{
size_t count{ 0 };
unsigned int buf_id{ 0 };
unsigned int tex_id{ 0 };
};
ActiveSet active_segments() const {
if (use_reduced_set())
return { m_enabled_segments_reduced_count, m_enabled_segments_reduced_buf_id, m_enabled_segments_reduced_tex_id };
return { m_enabled_segments_count, m_enabled_segments_buf_id, m_enabled_segments_tex_id };
}
ActiveSet active_options() const {
if (use_reduced_set())
return { m_enabled_options_reduced_count, m_enabled_options_reduced_buf_id, m_enabled_options_reduced_tex_id };
return { m_enabled_options_count, m_enabled_options_buf_id, m_enabled_options_tex_id };
}
#endif // ENABLE_OPENGL_ES
//
+15 -19
View File
@@ -236,7 +236,6 @@ set(SLIC3R_GUI_SOURCES
GUI/GLToolbar.hpp
GUI/ImageDPIFrame.cpp
GUI/ImageDPIFrame.hpp
GUI/IMediaController.hpp
GUI/GUI_App.cpp
GUI/GUI_App.hpp
GUI/GUI_AuxiliaryList.cpp
@@ -366,8 +365,6 @@ set(SLIC3R_GUI_SOURCES
GUI/MediaFilePanel.h
GUI/MediaPlayCtrl.cpp
GUI/MediaPlayCtrl.h
GUI/WebRtcMediaController.cpp
GUI/WebRtcMediaController.hpp
GUI/MeshUtils.cpp
GUI/MeshUtils.hpp
GUI/ModelMall.cpp
@@ -563,8 +560,6 @@ set(SLIC3R_GUI_SOURCES
GUI/WebUserLoginDialog.hpp
GUI/WebViewDialog.cpp
GUI/WebViewDialog.hpp
GUI/WebMediaController.hpp
GUI/WebMediaController.cpp
GUI/Widgets/AMSControl.cpp
GUI/Widgets/AMSControl.hpp
GUI/Widgets/AMSItem.cpp
@@ -759,7 +754,6 @@ set(SLIC3R_GUI_SOURCES
Utils/NetworkAgentFactory.cpp
Utils/ICloudServiceAgent.hpp
Utils/IPrinterAgent.hpp
Utils/ICameraSignalingChannel.hpp
Utils/OrcaCloudServiceAgent.cpp
Utils/OrcaCloudServiceAgent.hpp
Utils/OrcaPrinterAgent.cpp
@@ -930,7 +924,7 @@ else()
set(_opengl_link_lib OpenGL::GL)
endif()
target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo minilzo libvgcode md4c-html glad ${_opengl_link_lib} hidapi mdns ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto LibDataChannel::LibDataChannel noise::noise pybind11::embed)
target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo minilzo libvgcode md4c-html glad ${_opengl_link_lib} hidapi mdns ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto noise::noise pybind11::embed)
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
# Linux finds wxWidgets in module mode, whose include dirs and definitions
@@ -987,32 +981,29 @@ endif ()
if (APPLE)
# Static FFmpeg from the deps install: nothing to bundle into the .app,
# no rpath/install_name handling. Order matters: avformat -> avcodec -> swscale -> avutil.
find_library(LIBAVFORMAT_LIBRARY NAMES libavformat.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
# no rpath/install_name handling. Order matters: avcodec -> swscale -> avutil.
find_library(LIBAVCODEC_LIBRARY NAMES libavcodec.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBSWSCALE_LIBRARY NAMES libswscale.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBAVUTIL_LIBRARY NAMES libavutil.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
if (NOT LIBAVFORMAT_LIBRARY OR NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY)
message(FATAL_ERROR "Static FFmpeg (libavformat.a/libavcodec.a/libswscale.a/libavutil.a) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps — FFMPEG builds static-only on macOS.")
if (NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY)
message(FATAL_ERROR "Static FFmpeg (libavcodec.a/libswscale.a/libavutil.a) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps — FFMPEG builds static-only on macOS.")
endif ()
target_link_libraries(libslic3r_gui ${LIBAVFORMAT_LIBRARY} ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
target_link_libraries(libslic3r_gui ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include)
elseif (WIN32)
# Prebuilt shared FFmpeg from the deps install. Windows has no pkg-config,
# so resolve the import libraries out of the deps prefix directly; the DLLs
# are copied next to the executable by the top level CMakeLists.
find_library(LIBAVFORMAT_LIBRARY NAMES avformat PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBAVCODEC_LIBRARY NAMES avcodec PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBSWSCALE_LIBRARY NAMES swscale PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBAVUTIL_LIBRARY NAMES avutil PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
if (NOT LIBAVFORMAT_LIBRARY OR NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY)
message(FATAL_ERROR "FFmpeg (avformat/avcodec/swscale/avutil) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps.")
if (NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY)
message(FATAL_ERROR "FFmpeg (avcodec/swscale/avutil) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps.")
endif ()
target_link_libraries(libslic3r_gui ${LIBAVFORMAT_LIBRARY} ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
target_link_libraries(libslic3r_gui ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include)
else ()
pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET
libavformat
libavcodec
libswscale
libavutil
@@ -1028,9 +1019,14 @@ if (UNIX AND NOT APPLE)
find_package(GTK${SLIC3R_GTK} REQUIRED)
pkg_check_modules(LIBSECRET REQUIRED libsecret-1)
pkg_check_modules(webkit2gtk REQUIRED webkit2gtk-4.1)
find_package(X11 REQUIRED)
if (FLATPAK)
# I don't know why this is needed, but for whatever reason slic3r isn't
# linking to X11 and webkit2gtk. force it.
find_package(X11 REQUIRED)
target_link_libraries(libslic3r_gui ${X11_LIBRARIES} ${webkit2gtk_LIBRARIES})
endif()
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${GTK${SLIC3R_GTK}_INCLUDE_DIRS} ${LIBSECRET_INCLUDE_DIRS} ${webkit2gtk_INCLUDE_DIRS})
target_link_libraries(libslic3r_gui ${GTK${SLIC3R_GTK}_LIBRARIES} fontconfig ${LIBSECRET_LIBRARIES} ${webkit2gtk_LIBRARIES} ${X11_LIBRARIES})
target_link_libraries(libslic3r_gui ${GTK${SLIC3R_GTK}_LIBRARIES} fontconfig ${LIBSECRET_LIBRARIES})
# Propagate GDK backend detection results as compile definitions so that
# LinuxDisplayBackend.cpp can include the right GDK headers.
-27
View File
@@ -45,25 +45,6 @@ int AVVideoDecoder::open(Bambu_StreamInfo const &info)
return 0;
}
int AVVideoDecoder::open(AVCodecParameters const &parameters)
{
if (avcodec_parameters_to_context(codec_ctx_, &parameters) < 0)
return -1;
auto codec = avcodec_find_decoder(codec_ctx_->codec_id);
if (codec == nullptr) {
fprintf(stderr, "AVVideoDecoder: unsupported codec!\n");
return -1;
}
if (avcodec_open2(codec_ctx_, codec, nullptr) < 0) {
fprintf(stderr, "AVVideoDecoder: could not open codec\n");
return -1;
}
frame_ = av_frame_alloc();
return frame_ == nullptr ? -1 : 0;
}
int AVVideoDecoder::decode(const Bambu_Sample &sample)
{
int ret = -1;
@@ -90,14 +71,6 @@ int AVVideoDecoder::decode(const Bambu_Sample &sample)
return ret;
}
int AVVideoDecoder::decode(const AVPacket &packet)
{
int ret = avcodec_send_packet(codec_ctx_, &packet);
if (ret == 0)
got_frame_ = avcodec_receive_frame(codec_ctx_, frame_) == 0;
return ret;
}
int AVVideoDecoder::flush()
{
int ret = avcodec_send_packet(codec_ctx_, nullptr);
-10
View File
@@ -23,10 +23,8 @@ public:
public:
int open(Bambu_StreamInfo const &info);
int open(AVCodecParameters const &parameters);
int decode(Bambu_Sample const &sample);
int decode(AVPacket const &packet);
int flush();
@@ -36,14 +34,6 @@ public:
bool toWxBitmap(wxBitmap &bitmap, wxSize const & size);
// Native size of the most recently decoded frame, or an unspecified size if
// nothing has decoded yet. Lets a caller learn the video dimensions when the
// container/probe could not report them up front.
wxSize decoded_frame_size() const
{
return got_frame_ && frame_ ? wxSize{frame_->width, frame_->height} : wxSize{};
}
private:
AVCodecContext *codec_ctx_ = nullptr;
AVFrame * frame_ = nullptr;
+1 -1
View File
@@ -428,7 +428,7 @@ void AuFile::on_dclick(wxMouseEvent &evt)
if (m_type == AddFileButton)
return;
else
desktop_open_project_attachment(this, m_file_path);
wxLaunchDefaultApplication(m_file_path.wstring(), 0);
}
void AuFile::on_mouse_left_up(wxMouseEvent &evt)
+1 -1
View File
@@ -328,7 +328,7 @@ void SelectMObjectPopup::update_user_devices()
}
m_bind_machine_list.clear();
m_bind_machine_list = dev->get_my_machine_list(dev->get_current_printer_agent_id());
m_bind_machine_list = dev->get_my_machine_list();
//sort list
std::vector<std::pair<std::string, MachineObject*>> user_machine_list;
+29 -16
View File
@@ -6,10 +6,8 @@
#include "Widgets/Label.hpp"
#include "MsgDialog.hpp"
#include "libslic3r/Print.hpp"
#include "PrePrintChecker.hpp"
#include "DeviceCore/DevConfig.h"
#include "DeviceCore/DevConfigUtil.h"
#include "DeviceCore/DevExtruderSystem.h"
#include "DeviceCore/DevFilaBlackList.h"
#include "DeviceCore/DevFilaSystem.h"
@@ -1639,6 +1637,29 @@ void CalibrationPresetPage::update_combobox_filaments(MachineObject* obj)
select_default_compatible_filament();
}
bool CalibrationPresetPage::is_blocking_printing()
{
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!dev) return true;
MachineObject* obj_ = dev->get_selected_machine();
if (obj_ == nullptr) return true;
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
auto source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
auto target_model = obj_->printer_type;
if (source_model != target_model) {
std::vector<std::string> compatible_machine = obj_->get_compatible_machine();
vector<std::string>::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model);
if (it == compatible_machine.end()) {
return true;
}
}
return false;
}
bool CalibrationPresetPage::is_nozzle_info_synced() const
{
if (!curr_obj || !curr_obj->is_info_ready())
@@ -1720,13 +1741,11 @@ void CalibrationPresetPage::update_show_status()
}
}
bool has_optional_printer_model = DevPrinterConfigUtil::is_optional_printer_model_id(obj_->printer_type);
if (PresetBundle *preset_bundle = wxGetApp().preset_bundle) {
has_optional_printer_model = has_optional_printer_model ||
DevPrinterConfigUtil::is_optional_printer_model_id(
preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle));
}
//if (is_blocking_printing()) {
// show_status(CaliPresetPageStatus::CaliPresetStatusUnsupportedPrinter);
// return;
//}
//else
if (obj_->is_connecting() || !obj_->is_connected()) {
show_status(CaliPresetPageStatus::CaliPresetStatusInConnecting);
return;
@@ -1778,9 +1797,7 @@ void CalibrationPresetPage::update_show_status()
return;
}
show_status(has_optional_printer_model ?
CaliPresetPageStatus::CaliPresetStatusOptionalPrinterModel :
CaliPresetPageStatus::CaliPresetStatusNormal);
show_status(CaliPresetPageStatus::CaliPresetStatusNormal);
}
@@ -1834,10 +1851,6 @@ void CalibrationPresetPage::show_status(CaliPresetPageStatus status)
Layout();
Fit();
}
else if (status == CaliPresetPageStatus::CaliPresetStatusOptionalPrinterModel) {
update_print_status_msg(PrePrintChecker::get_pre_state_msg(PrintDialogStatus::PrintStatusOptionalPrinterModel), true);
Enable_Send_Button(true);
}
else if (status == CaliPresetPageStatus::CaliPresetStatusNoUserLogin) {
wxString msg_text = _L("No login account, only printers in LAN mode are displayed.");
update_print_status_msg(msg_text, false);
@@ -152,8 +152,7 @@ enum CaliPresetPageStatus
CaliPresetStatusInConnecting,
CaliPresetStatusFilamentIncompatible,
CaliPresetStatusLanModeSDcardNotAvailable,
CaliPresetStatusDifferentNozzleDiameters,
CaliPresetStatusOptionalPrinterModel
CaliPresetStatusDifferentNozzleDiameters
};
class CalibrationPresetPage : public CalibrationWizardPage
@@ -269,6 +268,7 @@ protected:
bool is_nozzle_info_synced() const;
void show_status(CaliPresetPageStatus status);
void Enable_Send_Button(bool enable);
bool is_blocking_printing();
bool need_check_sdcard(MachineObject* obj);
CaliPresetPageStatus get_status() { return m_page_status; }
+60
View File
@@ -28,6 +28,7 @@ wxEND_EVENT_TABLE()
wxDEFINE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
wxDEFINE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
wxDEFINE_EVENT(EVT_CAM_SOURCE_CHANGE, wxCommandEvent);
#define CAMERAPOPUP_CLICK_INTERVAL 20
@@ -101,6 +102,34 @@ CameraPopup::CameraPopup(wxWindow *parent)
top_sizer->Add(0, 0, wxALL, 0);
}
// Orca: custom IP camera source — lets the user point Live Video at any camera URL (Orca feature; not in the reference)
m_custom_camera_input_confirm = new Button(m_panel, _L("Enable"));
m_custom_camera_input_confirm->SetBackgroundColor(wxColour(38, 166, 154));
m_custom_camera_input_confirm->SetBorderColor(wxColour(38, 166, 154));
m_custom_camera_input_confirm->SetTextColor(wxColour(0xFFFFFE));
m_custom_camera_input_confirm->SetFont(Label::Body_14);
m_custom_camera_input_confirm->SetMinSize(wxSize(FromDIP(90), FromDIP(30)));
m_custom_camera_input_confirm->SetPosition(wxDefaultPosition);
m_custom_camera_input_confirm->SetCornerRadius(FromDIP(12));
m_custom_camera_input = new TextInput(m_panel, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, wxDefaultSize);
m_custom_camera_input->GetTextCtrl()->SetHint(_L("Hostname or IP"));
m_custom_camera_input->GetTextCtrl()->SetFont(Label::Body_14);
m_custom_camera_hint = new wxStaticText(m_panel, wxID_ANY, _L("Custom camera source"));
m_custom_camera_hint->Wrap(-1);
m_custom_camera_hint->SetFont(Label::Head_14);
m_custom_camera_hint->SetForegroundColour(TEXT_COL);
m_custom_camera_input_confirm->Bind(wxEVT_BUTTON, &CameraPopup::on_camera_source_changed, this);
if (!wxGetApp().app_config->get("camera", "custom_source").empty()) {
m_custom_camera_input->GetTextCtrl()->SetValue(wxGetApp().app_config->get("camera", "custom_source"));
set_custom_cam_button_state(wxGetApp().app_config->get("camera", "enable_custom_source") == "true");
}
top_sizer->Add(m_custom_camera_hint, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, FromDIP(5));
top_sizer->Add(0, 0, wxALL, 0);
top_sizer->Add(m_custom_camera_input, 2, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, FromDIP(5));
top_sizer->Add(m_custom_camera_input_confirm, 1, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, FromDIP(5));
main_sizer->Add(top_sizer, 0, wxALL, FromDIP(10));
auto url = wxString(L"https://www.orcaslicer.com/wiki/"); // Orca: neutral wiki link (vendor URL removed)
@@ -155,6 +184,37 @@ void CameraPopup::sdcard_absent_hint()
GetEventHandler()->ProcessEvent(evt);
}
void CameraPopup::on_camera_source_changed(wxCommandEvent &event)
{
if (m_obj && !m_custom_camera_input->GetTextCtrl()->IsEmpty()) {
handle_camera_source_change();
}
}
void CameraPopup::handle_camera_source_change()
{
m_custom_camera_enabled = !m_custom_camera_enabled;
set_custom_cam_button_state(m_custom_camera_enabled);
wxGetApp().app_config->set("camera", "custom_source", m_custom_camera_input->GetTextCtrl()->GetValue().ToStdString());
wxGetApp().app_config->set("camera", "enable_custom_source", m_custom_camera_enabled);
wxCommandEvent evt(EVT_CAM_SOURCE_CHANGE);
evt.SetEventObject(this);
GetEventHandler()->ProcessEvent(evt);
}
void CameraPopup::set_custom_cam_button_state(bool state)
{
m_custom_camera_enabled = state;
auto stateColour = state ? wxColour(170, 0, 0) : wxColour(38, 166, 154);
auto stateText = state ? "Disable" : "Enable";
m_custom_camera_input_confirm->SetBackgroundColor(stateColour);
m_custom_camera_input_confirm->SetBorderColor(stateColour);
m_custom_camera_input_confirm->SetLabel(_L(stateText));
}
void CameraPopup::on_switch_recording(wxCommandEvent& event)
{
if (!m_obj) return;
+9 -4
View File
@@ -15,12 +15,14 @@
#include "Widgets/SwitchButton.hpp"
#include "Widgets/RadioBox.hpp"
#include "Widgets/PopupWindow.hpp"
#include "Widgets/TextInput.hpp"
namespace Slic3r {
namespace GUI {
wxDECLARE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
wxDECLARE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
wxDECLARE_EVENT(EVT_CAM_SOURCE_CHANGE, wxCommandEvent);
class CameraPopup : public PopupWindow
{
@@ -51,6 +53,9 @@ protected:
void on_switch_recording(wxCommandEvent& event);
void on_set_resolution();
void sdcard_absent_hint();
void on_camera_source_changed(wxCommandEvent& event);
void handle_camera_source_change();
void set_custom_cam_button_state(bool state);
wxWindow * create_item_radiobox(wxString title, wxWindow *parent, wxString tooltip, int padding_left);
void select_curr_radiobox(int btn_idx);
@@ -71,10 +76,10 @@ private:
wxStaticText* m_text_liveview_retry;
SwitchButton* m_switch_liveview_retry;
#endif //BBL_RELEASE_TO_PUBLIC
// wxStaticText* m_custom_camera_hint;
// TextInput* m_custom_camera_input;
// Button* m_custom_camera_input_confirm;
// bool m_custom_camera_enabled{ false };
wxStaticText* m_custom_camera_hint;
TextInput* m_custom_camera_input;
Button* m_custom_camera_input_confirm;
bool m_custom_camera_enabled{ false };
wxStaticText* m_text_resolution;
wxWindow* m_resolution_options[RESOLUTION_OPTIONS_NUM];
wxScrolledWindow *m_panel;
+6 -8
View File
@@ -171,16 +171,14 @@ void ConfigManipulation::check_adaptive_pressure_advance_model(DynamicPrintConfi
return;
const auto* model = config->option<ConfigOptionStrings>("adaptive_pressure_advance_model");
if (model == nullptr)
if (model == nullptr || model->values.empty())
return;
// Each extruder variant holds its own model.
std::string error;
for (const std::string& variant_model : model->values) {
error = AdaptivePAProcessor::validate_adaptive_pa_model(variant_model);
if (!error.empty())
break;
}
std::string raw_model;
for (const std::string& chunk : model->values)
raw_model += chunk;
std::string error = AdaptivePAProcessor::validate_adaptive_pa_model(raw_model);
if (!error.empty()) {
wxString msg_text = _L("Adaptive Pressure Advance model validation failed:\n");
msg_text += from_u8(error);
+1 -3
View File
@@ -35,9 +35,7 @@ ConnectPrinterDialog::ConnectPrinterDialog(wxWindow *parent, wxWindowID id, cons
sizer_connect = new wxBoxSizer(wxHORIZONTAL);
m_textCtrl_code = new TextInput(this, wxEmptyString);
// OrcaSonar uses a 12-character base32 access code. Keep this field long
// enough for it while retaining the existing validation for LAN codes.
m_textCtrl_code->GetTextCtrl()->SetMaxLength(12);
m_textCtrl_code->GetTextCtrl()->SetMaxLength(10);
m_textCtrl_code->SetFont(Label::Body_14);
m_textCtrl_code->SetCornerRadius(FromDIP(5));
m_textCtrl_code->SetSize(wxSize(FromDIP(330), FromDIP(40)));
@@ -1,7 +1,5 @@
#include "DevConfigUtil.h"
#include "slic3r/GUI/DeviceManager.hpp"
#include <wx/dir.h>
#include <boost/filesystem/operations.hpp>
#include "../I18N.hpp"
@@ -43,19 +41,6 @@ static void _toolhead_translation_markers()
std::string DevPrinterConfigUtil::m_resource_file_path = "";
bool DevPrinterConfigUtil::is_printer_model_compatible(const std::string& source_model, MachineObject& machine)
{
const std::string& target_model = machine.printer_type;
if (is_optional_printer_model_id(source_model) || is_optional_printer_model_id(target_model))
return true;
if (source_model == target_model)
return true;
const auto compatible_machine = machine.get_compatible_machine();
return std::find(compatible_machine.begin(), compatible_machine.end(), source_model) != compatible_machine.end();
}
std::map<std::string, std::string> DevPrinterConfigUtil::get_all_model_id_with_name()
{
@@ -25,8 +25,6 @@
namespace Slic3r
{
class MachineObject;
/// Toolhead component type (extruder / nozzle / hotend)
enum class ToolHeadComponent {
Extruder,
@@ -61,10 +59,6 @@ public:
/*printer*/
// info
static std::map<std::string, std::string> get_all_model_id_with_name();
// A printer agent may not know the physical model. Keep that case optional so
// model compatibility checks do not turn missing identity into a hard error.
static bool is_printer_model_compatible(const std::string& source_model, MachineObject& machine);
static bool is_optional_printer_model_id(const std::string& model_id) { return model_id.empty(); }
static std::string get_printer_type(const std::string& type_str) { return get_value_from_config<std::string>(type_str, "printer_type"); }
static std::string get_printer_display_name(const std::string& type_str) { return get_value_from_config<std::string>(type_str, "display_name"); }
static std::string get_printer_series_str(std::string type_str) { return get_value_from_config<std::string>(type_str, "printer_series"); }
+3 -3
View File
@@ -762,9 +762,9 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
{
curr_tray->remain = -1;
}
// The tray objects are reused across status updates. Reset this
// state when a previously empty slot receives a filament again.
curr_tray->is_slot_placeholder = tray_it->contains("tray_slot_placeholder");
if (tray_it->contains("tray_slot_placeholder")) {
curr_tray->is_slot_placeholder = true;
}
int ams_id_int = 0;
int tray_id_int = 0;
try
+52 -133
View File
@@ -1,10 +1,8 @@
#include "DevManager.h"
#include <nlohmann/json.hpp>
#include <exception>
#include <libslic3r/AppConfig.hpp>
#include "CloudProvider.hpp"
#include "DevManager.h"
#include "DevUtil.h"
// TODO: remove this include
@@ -17,8 +15,6 @@
#include "libslic3r/Time.hpp"
#include "IPrinterAgent.hpp"
using namespace nlohmann;
namespace {
@@ -50,12 +46,10 @@ namespace {
namespace Slic3r
{
DeviceManager::DeviceManager(NetworkAgent* agent, bool enable_refresher, AppConfig* app_config)
DeviceManager::DeviceManager(NetworkAgent* agent)
{
m_agent = agent;
m_app_config = app_config;
if (enable_refresher)
m_refresher = new DeviceManagerRefresher(this);
m_agent = agent;
m_refresher = new DeviceManagerRefresher(this);
DevPrinterConfigUtil::InitFilePath(resources_dir());
@@ -66,14 +60,9 @@ namespace Slic3r
}
}
AppConfig* DeviceManager::get_app_config() const
{
return m_app_config ? m_app_config : GUI::wxGetApp().app_config;
}
void DeviceManager::load_local_machines_from_config()
{
AppConfig* config = get_app_config();
AppConfig* config = GUI::wxGetApp().app_config;
if (!config)
return;
const auto local_machines = config->get_local_machines();
@@ -99,8 +88,9 @@ namespace Slic3r
}
}
void DeviceManager::update_local_machine(const MachineObject& m, AppConfig* config)
void DeviceManager::update_local_machine(const MachineObject& m)
{
AppConfig* config = GUI::wxGetApp().app_config;
if (config) {
if (m.is_lan_mode_printer()) {
if (m.has_access_right()) {
@@ -121,8 +111,7 @@ namespace Slic3r
DeviceManager::~DeviceManager()
{
if (m_refresher)
delete m_refresher;
delete m_refresher;
for (auto it = localMachineList.begin(); it != localMachineList.end(); it++)
{
@@ -182,22 +171,14 @@ namespace Slic3r
return printer_agent ? printer_agent->get_agent_info().id : "";
}
std::string DeviceManager::get_current_cloud_provider() const
{
const std::string agent_id = get_current_printer_agent_id();
if (!agent_id.empty())
return agent_id == BBL_PRINTER_AGENT_ID ? BBL_CLOUD_PROVIDER : ORCA_CLOUD_PROVIDER;
return GUI::wxGetApp().get_printer_cloud_provider();
}
void DeviceManager::EnableMultiMachine(bool enable)
{
m_agent->enable_multi_machine(enable);
m_enable_mutil_machine = enable;
}
void DeviceManager::start_refresher() { if (m_refresher) m_refresher->Start(); }
void DeviceManager::stop_refresher() { if (m_refresher) m_refresher->Stop(); }
void DeviceManager::start_refresher() { m_refresher->Start(); }
void DeviceManager::stop_refresher() { m_refresher->Stop(); }
void DeviceManager::keep_alive()
@@ -284,10 +265,6 @@ namespace Slic3r
/* update userMachineList info */
auto it = userMachineList.find(dev_id);
if (it != userMachineList.end()) {
// A reused entry may have been created while another printer agent was active.
// The response was obtained through the current agent, so move ownership with
// the entry; otherwise agent-scoped lists hide it after a preset switch.
it->second->printer_agent_id = get_current_printer_agent_id();
if (it->second->get_dev_ip() != dev_ip ||
it->second->bind_state != bind_state ||
it->second->bind_sec_link != sec_link ||
@@ -314,15 +291,10 @@ namespace Slic3r
/* update localMachineList */
it = localMachineList.find(dev_id);
AppConfig* config = get_app_config();
if (it != localMachineList.end()) {
// update properties
/* ip changed */
obj = it->second;
// A reused LAN entry may have been discovered while another printer agent was
// active. The current discovery message establishes ownership for this agent.
obj->printer_agent_id = get_current_printer_agent_id();
if (obj->get_dev_ip().compare(dev_ip) != 0) {
if ( connection_name.empty() ) {
@@ -412,6 +384,7 @@ namespace Slic3r
obj->m_is_online = true;
//load access code
AppConfig* config = Slic3r::GUI::wxGetApp().app_config;
if (config) {
obj->set_access_code(get_access_code_with_legacy_fallback(config, dev_id, obj->printer_agent_id), false);
}
@@ -434,7 +407,7 @@ namespace Slic3r
fire_lifecycle_event(LifecycleEvent::DeviceDiscovered, ctx);
}
}
update_local_machine(*obj, config);
update_local_machine(*obj);
}
catch (...) {
;
@@ -449,9 +422,6 @@ namespace Slic3r
auto it = localMachineList.find(machine.dev_id);
if (it != localMachineList.end()) {
obj = it->second;
// insert_local_device is called by the active agent, so a reused entry must follow
// that agent as well; otherwise the agent-scoped printer list hides it.
obj->printer_agent_id = get_current_printer_agent_id();
} else {
obj = new MachineObject(this, m_agent, machine.dev_name, machine.dev_id, machine.dev_ip);
obj->printer_agent_id = get_current_printer_agent_id();
@@ -469,16 +439,11 @@ namespace Slic3r
obj->last_alive = Slic3r::Utils::get_current_time_utc();
obj->set_access_code(access_code, false);
update_local_machine(*obj, get_app_config());
update_local_machine(*obj);
return obj;
}
void DeviceManager::update_local_machine(const MachineObject& m)
{
update_local_machine(m, GUI::wxGetApp().app_config);
}
int DeviceManager::query_bind_status(std::string& msg, const std::string& provider)
{
if (!m_agent)
@@ -549,7 +514,7 @@ namespace Slic3r
MachineObject* DeviceManager::get_my_machine(std::string dev_id)
{
auto list = get_my_machine_list(get_current_printer_agent_id());
auto list = get_my_machine_list();
auto it = list.find(dev_id);
if (it != list.end())
{
@@ -586,15 +551,25 @@ namespace Slic3r
OnSelectedMachineChanged(previous_selected_machine, selected_machine);
}
void DeviceManager::clear_other_devices()
void DeviceManager::clear_other_devices(const std::string& target_agent_id)
{
// Device entries are now scoped by printer_agent_id when they are presented. Keep
// agent-owned discoveries across a switch so agents without automatic discovery (and
// plugins whose devices have not received an access code yet) do not lose their list.
// Entries without an owner are legacy/unscoped and cannot safely be shown.
// why: on agent swap, keep "My Devices" but drop the transient "Other Devices"
// Those belong to the previous agent's network scan; the new agent's start_discovery re-populates its own.
//
// Also drop "My Devices" stamped by a different agent than the one we're swapping to
// (target_agent_id, passed by the caller since the live agent hasn't been repointed yet
// at this point): otherwise a device first discovered under agent A survives every swap
// with a stale printer_agent_id, stays hidden from every agent's filtered list, and only
// gets re-tagged if something happens to delete and re-create it (e.g. account logout).
// Dropping it here instead lets the new agent's start_discovery re-insert and re-stamp it
// like any other fresh device.
const auto my = get_my_machine_list();
for (auto it = localMachineList.begin(); it != localMachineList.end();)
{
if (!it->second || it->second->printer_agent_id.empty())
const bool is_my_device = my.find(it->first) != my.end();
const bool agent_mismatch = !target_agent_id.empty() && it->second &&
it->second->printer_agent_id != target_agent_id;
if (!is_my_device || agent_mismatch)
{
delete it->second;
it = localMachineList.erase(it);
@@ -610,22 +585,8 @@ namespace Slic3r
{
BOOST_LOG_TRIVIAL(info) << "set_selected_machine=" << dev_id
<< " cur_selected=" << selected_machine;
auto my_machine_list = get_my_machine_list(get_current_printer_agent_id());
auto my_machine_list = get_my_machine_list();
auto it = my_machine_list.find(dev_id);
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: set_selected_machine lookup dev_id=" << dev_id
<< " found=" << (it != my_machine_list.end())
<< " my_machine_count=" << my_machine_list.size()
<< " current_agent=" << get_current_printer_agent_id()
<< " provider=" << GUI::wxGetApp().get_printer_cloud_provider();
if (it != my_machine_list.end() && it->second) {
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: target machine dev_id=" << it->second->get_dev_id()
<< " printer_agent_id=" << it->second->printer_agent_id
<< " connection_type=" << it->second->connection_type()
<< " dev_connection_type=" << it->second->dev_connection_type;
} else if (!dev_id.empty()) {
BOOST_LOG_TRIVIAL(warning) << "Orca diagnostic: target machine was not found in the current agent's machine list";
return false;
}
// disconnect last if dev_id difference from previous one
auto last_selected = my_machine_list.find(selected_machine);
@@ -636,9 +597,7 @@ namespace Slic3r
m_agent->disconnect_printer();
}
else if (last_selected->second->connection_type() == "cloud") {
const int result = m_agent->set_user_selected_machine("");
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: cleared previous cloud selection dev_id="
<< selected_machine << " result=" << result;
m_agent->set_user_selected_machine("");
}
}
@@ -673,7 +632,11 @@ namespace Slic3r
m_agent->disconnect_printer();
it->second->reset();
it->second->connect();
#if !BBL_RELEASE_TO_PUBLIC
it->second->connect(Slic3r::GUI::wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false);
#else
it->second->connect(it->second->local_use_ssl);
#endif
it->second->set_lan_mode_connection_state(true);
}
}
@@ -686,16 +649,18 @@ namespace Slic3r
{
// diff dev_id, cloud => set_user_selected_machine(new)
BOOST_LOG_TRIVIAL(info) << "set_selected_machine: select new cloud machine, dev_id =" << dev_id;
const int result = m_agent->set_user_selected_machine(dev_id);
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: set new cloud selection dev_id="
<< dev_id << " result=" << result;
m_agent->set_user_selected_machine(dev_id);
it->second->reset();
}
else
{
BOOST_LOG_TRIVIAL(info) << "set_selected_machine: select new lan machine, dev_id =" << dev_id;
it->second->reset();
it->second->connect();
#if !BBL_RELEASE_TO_PUBLIC
it->second->connect(Slic3r::GUI::wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false);
#else
it->second->connect(it->second->local_use_ssl);
#endif
it->second->set_lan_mode_connection_state(true);
}
}
@@ -712,8 +677,6 @@ namespace Slic3r
selected_machine = dev_id;
record_user_last_machine(selected_machine);
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: DeviceManager selection complete selected_machine="
<< selected_machine;
return true;
}
@@ -744,9 +707,7 @@ namespace Slic3r
dev_list.push_back(it->first);
BOOST_LOG_TRIVIAL(trace) << "add_user_subscribe: " << it->first;
}
const int result = m_agent->add_subscribe(dev_list);
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: add_user_subscribe count=" << dev_list.size()
<< " result=" << result;
m_agent->add_subscribe(dev_list);
}
@@ -759,9 +720,7 @@ namespace Slic3r
dev_list.push_back(it->first);
BOOST_LOG_TRIVIAL(trace) << "del_user_subscribe: " << it->first;
}
const int result = m_agent->del_subscribe(dev_list);
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: del_user_subscribe count=" << dev_list.size()
<< " result=" << result;
m_agent->del_subscribe(dev_list);
}
void DeviceManager::subscribe_device_list(std::vector<std::string> dev_list)
@@ -880,24 +839,7 @@ namespace Slic3r
try
{
json j = json::parse(body);
const bool has_request_context = j.contains("provider") && j.contains("agent_id") && j.contains("generation");
const std::string current_provider = get_current_cloud_provider();
const std::string provider = j.contains("provider") ? j["provider"].get<std::string>()
: current_provider;
const std::string agent_id = j.contains("agent_id") ? j["agent_id"].get<std::string>()
: get_current_printer_agent_id();
const std::uint64_t generation = j.value("generation", std::uint64_t(0));
if (has_request_context &&
(provider != current_provider ||
agent_id != get_current_printer_agent_id() ||
generation != (m_agent ? m_agent->get_user_machine_list_generation() : 0))) {
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ": ignoring stale response provider="
<< provider << " agent_id=" << agent_id
<< " generation=" << generation;
return;
}
const std::string provider = GUI::wxGetApp().get_printer_cloud_provider();
#if !BBL_RELEASE_TO_PUBLIC
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << j;
@@ -920,14 +862,11 @@ namespace Slic3r
/* update field */
obj = iter->second;
obj->set_dev_id(dev_id);
// A device can be rediscovered by a different agent after a preset
// switch while retaining the same MachineObject instance.
obj->printer_agent_id = agent_id;
}
else
{
obj = new MachineObject(this, m_agent, "", "", "");
obj->printer_agent_id = agent_id;
obj->printer_agent_id = get_current_printer_agent_id();
if (m_agent)
{
obj->set_bind_status(m_agent->get_user_name(provider));
@@ -935,20 +874,13 @@ namespace Slic3r
if (obj->get_dev_ip().empty())
{
if (AppConfig* config = get_app_config())
obj->get_dev_ip() = config->get("ip_address", dev_id);
obj->get_dev_ip() = Slic3r::GUI::wxGetApp().app_config->get("ip_address", dev_id);
}
userMachineList.insert(std::make_pair(dev_id, obj));
}
if (!obj) continue;
// Orca cloud printers are only ever delivered through this REST
// account list; tag them so DeviceManager's cloud/lan branches
// (subscribe + deselect in set_selected_machine) treat them right.
if (provider == ORCA_CLOUD_PROVIDER)
obj->dev_connection_type = "cloud";
if (!elem["dev_id"].is_null())
obj->set_dev_id(elem["dev_id"].get<std::string>());
if (!elem["dev_name"].is_null())
@@ -980,12 +912,6 @@ namespace Slic3r
acc_code.erase(std::remove(acc_code.begin(), acc_code.end(), '\n'), acc_code.end());
obj->set_access_code(acc_code);
}
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: parsed cloud machine dev_id=" << dev_id
<< " name=" << obj->get_dev_name()
<< " agent_id=" << obj->printer_agent_id
<< " connection_type=" << obj->connection_type()
<< " online=" << obj->m_is_online;
}
//remove MachineObject from userMachineList
@@ -1001,9 +927,6 @@ namespace Slic3r
iterat++;
}
}
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: parse_user_print_info complete provider=" << provider
<< " parsed_count=" << new_list.size()
<< " stored_count=" << userMachineList.size();
}
}
catch (std::exception& e)
@@ -1020,29 +943,25 @@ namespace Slic3r
unsigned int http_code;
std::string body;
int result = m_agent->get_user_print_info(&http_code, &body, provider);
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: get_user_print_info provider=" << provider
<< " result=" << result << " http_code=" << http_code
<< " body_bytes=" << body.size();
if (result == 0)
{
// parse_user_print_info and on_machine_alive (SSDP for discovery) both mutate the same userMachineList map.
// on_machine_alive mutates the map on the UI thread, do the same for parse_user_print_info.
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: queueing parse_user_print_info on UI thread";
Slic3r::GUI::wxGetApp().CallAfter([this, body]() { parse_user_print_info(body); });
}
}
void DeviceManager::record_user_last_machine(const std::string& dev_id)
{
if (AppConfig* config = get_app_config()) {
config->set("user_last_selected_machine", dev_id);
if (Slic3r::GUI::wxGetApp().app_config) {
Slic3r::GUI::wxGetApp().app_config->set("user_last_selected_machine", dev_id);
}
}
std::string DeviceManager::get_user_last_machine() const
{
if (AppConfig* config = get_app_config()) {
const auto& user_last_machine = config->get("user_last_selected_machine");
if (Slic3r::GUI::wxGetApp().app_config) {
const auto& user_last_machine = Slic3r::GUI::wxGetApp().app_config->get("user_last_selected_machine");
if (!user_last_machine.empty()) {
return user_last_machine;
} else if (m_agent) {
+8 -13
View File
@@ -12,7 +12,6 @@ namespace Slic3r
struct BBLocalMachine;
class MachineObject;
class NetworkAgent;
class AppConfig;
namespace GUI {
class GUI_App;
@@ -25,7 +24,6 @@ class DeviceManager
friend class DeviceManagerRefresher;
private:
NetworkAgent* m_agent{ nullptr };
AppConfig* m_app_config{ nullptr };
DeviceManagerRefresher* m_refresher{ nullptr };
bool m_enable_mutil_machine = false;
@@ -37,13 +35,11 @@ private:
std::map<std::string, MachineObject*> userMachineList; /* dev_id -> MachineObject* cloudMachine of User */
public:
DeviceManager(NetworkAgent* agent = nullptr, bool enable_refresher = true,
AppConfig* app_config = nullptr);
DeviceManager(NetworkAgent* agent = nullptr);
~DeviceManager();
public:
NetworkAgent* get_agent() const { return m_agent; }
AppConfig* get_app_config() const;
void set_agent(NetworkAgent* agent);
void start_refresher();
@@ -78,9 +74,10 @@ public:
void erase_user_machine(std::string dev_id) { userMachineList.erase(dev_id); }
void clean_user_info(bool keep_local_selection = false);
// Retain agent-owned LAN discoveries across a switch; the active-agent list filter keeps
// entries from other agents hidden while allowing them to reappear when switched back.
void clear_other_devices();
// target_agent_id: id of the agent being swapped to (empty = no agent-mismatch check,
// just the original "drop Other Devices" behavior). Pass the incoming agent's id, not the
// live one - this runs before the live agent is repointed.
void clear_other_devices(const std::string& target_agent_id = "");
void load_last_machine();
void update_user_machine_list_info(const std::string& provider);
@@ -125,7 +122,6 @@ private:
void keep_alive();
void check_pushing();
std::string get_current_cloud_provider() const;
void OnMachineBindStateChanged(MachineObject* obj, const std::string& new_state);
void OnSelectedMachineChanged(const std::string& pre_dev_id, const std::string& new_dev_id);
@@ -138,15 +134,14 @@ public:
std::string connection_type, std::string bind_state, std::string version,
std::string access_code);
static void update_local_machine(const MachineObject& m);
static void update_local_machine(const MachineObject& m, AppConfig* config);
};
class DeviceManagerRefresher : public wxObject
{
wxTimer* m_timer{nullptr};
int m_timer_interval_msec = 5000;
wxTimer* m_timer{ nullptr };
int m_timer_interval_msec = 5000;
DeviceManager* m_manager{nullptr};
DeviceManager* m_manager{ nullptr };
public:
DeviceManagerRefresher(DeviceManager* manger);
+96 -213
View File
@@ -4,7 +4,6 @@
#include "I18N.hpp"
#include "libslic3r/Time.hpp"
#include "libslic3r/Thread.hpp"
#include "slic3r/Utils/Http.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "slic3r/Utils/NetworkAgentFactory.hpp"
@@ -18,7 +17,6 @@
#include "ReleaseNote.hpp"
#include <thread>
#include <mutex>
#include <charconv>
#include <codecvt>
#include <boost/foreach.hpp>
#include <boost/typeof/typeof.hpp>
@@ -56,7 +54,6 @@
#include "DeviceCore/DevStatus.h"
#include "DeviceCore/DevUpgrade.h"
#include "IPrinterAgent.hpp"
#define CALI_DEBUG
#define MINUTE_30 1800000 //ms
@@ -383,22 +380,8 @@ NozzleVolumeType convert_to_nozzle_type(const std::string &str)
wxString MachineObject::get_printer_type_display_str() const
{
std::string display_name = DevPrinterConfigUtil::get_printer_display_name(printer_type);
// Bambu printers use m_resource_file_path + "/printers/" + type_str + ".json", which is a semantic that only works for their profiles.
// For any other profile, we can simply consult preset bundle if the model_id exists.
if (display_name.empty()) {
for (const auto& [vendor_id, vendor] : GUI::wxGetApp().preset_bundle->vendors) {
for (const auto& model : vendor.models) {
if (printer_type == model.model_id)
display_name = model.name;
}
}
}
if (!display_name.empty())
return display_name;
else if (printer_type == "orcasonar")
return "OrcaSonar Printer";
else
return _L("Unknown");
}
@@ -485,7 +468,7 @@ void MachineObject::set_access_code(std::string code, bool only_refresh)
{
this->access_code = code;
if (only_refresh) {
AppConfig* config = m_manager ? m_manager->get_app_config() : GUI::wxGetApp().app_config;
AppConfig* config = GUI::wxGetApp().app_config;
if (config) {
if (is_lan_mode_printer()) {
// why: LAN codes are scoped via BBLocalMachine::access_code, keyed by dev_id and
@@ -498,7 +481,7 @@ void MachineObject::set_access_code(std::string code, bool only_refresh)
// fresh from the cloud API's current response, so there's no cross-agent leakage
// risk to guard against there.
if (!code.empty()) {
DeviceManager::update_local_machine(*this, config);
DeviceManager::update_local_machine(*this);
} else {
// Only patch an existing record's code - don't persist a brand-new
// never-bound entry just because set_access_code("") was called on it.
@@ -1365,6 +1348,7 @@ int MachineObject::command_get_access_code() {
return this->publish_json(j);
}
int MachineObject::command_request_push_all(bool request_now)
{
auto curr_time = std::chrono::system_clock::now();
@@ -1496,20 +1480,26 @@ int MachineObject::command_upgrade_module(std::string url, std::string module_ty
int MachineObject::command_xyz_abs()
{
if (!m_agent) return -1;
return command_with_dialog(m_agent->command_xyz_abs(get_dev_id(), MachineObject::m_sequence_id++, is_lan_mode_printer()));
return this->publish_gcode("G90 \n");
}
int MachineObject::command_auto_leveling()
{
if (!m_agent) return -1;
return command_with_dialog(m_agent->command_auto_leveling(get_dev_id(), MachineObject::m_sequence_id++, is_lan_mode_printer()));
return this->publish_gcode("G29 \n");
}
int MachineObject::command_go_home()
{
if (!m_agent) return -1;
return command_with_dialog(m_agent->command_go_home(get_dev_id(), this->is_in_printing(), m_support_mqtt_homing, MachineObject::m_sequence_id++, is_lan_mode_printer()));
if (m_support_mqtt_homing)
{
json j;
j["print"]["command"] = "back_to_center";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return this->publish_json(j);
}
// gcode command
return this->is_in_printing() ? this->publish_gcode("G28 X\n") : this->publish_gcode("G28 \n");
}
int MachineObject::command_task_partskip(std::vector<int> part_ids)
@@ -1631,14 +1621,23 @@ int MachineObject::command_stop_buzzer()
int MachineObject::command_set_bed(int temp)
{
if (!m_agent) return -1;
return command_with_dialog(m_agent->command_set_bed(get_dev_id(), temp, m_support_mqtt_bet_ctrl, MachineObject::m_sequence_id++, is_lan_mode_printer()));
if (m_support_mqtt_bet_ctrl)
{
json j;
j["print"]["command"] = "set_bed_temp";
j["print"]["temp"] = temp;
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return this->publish_json(j);
}
std::string gcode_str = (boost::format("M140 S%1%\n") % temp).str();
return this->publish_gcode(gcode_str);
}
int MachineObject::command_set_nozzle(int temp)
{
if (!m_agent) return -1;
return command_with_dialog(m_agent->command_set_nozzle(get_dev_id(), temp, MachineObject::m_sequence_id++, is_lan_mode_printer()));
std::string gcode_str = (boost::format("M104 S%1%\n") % temp).str();
return this->publish_gcode(gcode_str);
}
int MachineObject::command_set_nozzle_new(int nozzle_id, int temp)
@@ -1743,8 +1742,9 @@ int MachineObject::command_ams_user_settings(bool start_read_opt, bool tray_read
int MachineObject::command_ams_calibrate(int ams_id)
{
if (!m_agent) return -1;
return command_with_dialog(m_agent->command_ams_calibrate(get_dev_id(), ams_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
std::string gcode_cmd = (boost::format("M620 C%1% \n") % ams_id).str();
BOOST_LOG_TRIVIAL(trace) << "ams_debug: gcode_cmd" << gcode_cmd;
return this->publish_gcode(gcode_cmd);
}
int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max)
@@ -1780,23 +1780,29 @@ int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::s
return this->publish_json(j);
}
int MachineObject::command_ams_refresh_rfid(int ams_id, int slot_id)
int MachineObject::command_ams_refresh_rfid(std::string tray_id)
{
if (!m_agent) return -1;
return command_with_dialog(m_agent->command_ams_refresh_rfid(get_dev_id(), ams_id, slot_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
std::string gcode_cmd = (boost::format("M620 R%1% \n") % tray_id).str();
BOOST_LOG_TRIVIAL(trace) << "ams_debug: gcode_cmd" << gcode_cmd;
return this->publish_gcode(gcode_cmd);
}
int MachineObject::command_start_camera()
int MachineObject::command_ams_refresh_rfid2(int ams_id, int slot_id)
{
if (!m_agent) return -1;
return m_agent->command_start_camera(get_dev_id());
json j;
j["print"]["command"] = "ams_get_rfid";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["print"]["ams_id"] = ams_id;
j["print"]["slot_id"] = slot_id;
return this->publish_json(j);
}
int MachineObject::command_ams_select_tray(std::string tray_id)
{
if (!m_agent) return -1;
return command_with_dialog(m_agent->command_ams_select_tray(get_dev_id(), tray_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
std::string gcode_cmd = (boost::format("M620 P%1% \n") % tray_id).str();
BOOST_LOG_TRIVIAL(trace) << "ams_debug: gcode_cmd" << gcode_cmd;
return this->publish_gcode(gcode_cmd);
}
int MachineObject::command_ams_control(std::string action)
@@ -1955,10 +1961,47 @@ int MachineObject::command_ams_air_print_detect(bool air_print_detect)
int MachineObject::command_axis_control(std::string axis, double unit, double input_val, int speed)
{
if (!m_agent) return -1;
return command_with_dialog(m_agent->command_axis_control(get_dev_id(), axis, unit, input_val, speed, is_core_xy(),
m_support_mqtt_axis_control, MachineObject::m_sequence_id++,
is_lan_mode_printer()));
if (m_support_mqtt_axis_control)
{
int dir = input_val > 0 ? 1 : -1;
// i3-arch printers move the bed for Y/Z, so the on-screen direction is
// reversed — same negation the g-code fallback below applies.
if (!is_core_xy() && (axis.compare("Y") == 0 || axis.compare("Z") == 0)) {
dir = -dir;
}
json j;
j["print"]["command"] = "xyz_ctrl";
j["print"]["axis"] = axis;
j["print"]["dir"] = dir;
j["print"]["mode"] = (std::abs(input_val) >= 10) ? 1 : 0;
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return this->publish_json(j);
}
double value = input_val;
if (!is_core_xy()) {
if ( axis.compare("Y") == 0
|| axis.compare("Z") == 0) {
value = -1.0 * input_val;
}
}
char cmd[256];
if (axis.compare("X") == 0
|| axis.compare("Y") == 0
|| axis.compare("Z") == 0) {
sprintf(cmd, "M211 S \nM211 X1 Y1 Z1\nM1002 push_ref_mode\nG91 \nG1 %s%0.1f F%d\nM1002 pop_ref_mode\nM211 R\n", axis.c_str(), value * unit, speed);
}
else if (axis.compare("E") == 0) {
sprintf(cmd, "M83 \nG0 %s%0.1f F%d\n", axis.c_str(), value * unit, speed);
}
else {
return -1;
}
return this->publish_gcode(cmd);
}
int MachineObject::command_extruder_control(int nozzle_id, double val)
@@ -2583,12 +2626,7 @@ void MachineObject::reset()
vt_slot.erase(vt_slot.begin() + 1);
}
}
// why: reset reuses MachineObject, so release its lazy subtask
// before dropping the pointer to prevent reconnect leaks.
if (subtask_) {
delete subtask_;
subtask_ = nullptr;
}
subtask_ = nullptr;
has_extra_flow_type = false;
m_partskip_ids.clear();
}
@@ -2606,63 +2644,15 @@ void MachineObject::set_print_state(std::string status)
}
}
// why: printer agents can report progress without BBL cloud task identity.
void MachineObject::update_print_progress(const json& value)
{
if (value.is_string()) {
const std::string progress = value.get<std::string>();
int parsed_progress;
const auto result = std::from_chars(progress.data(), progress.data() + progress.size(), parsed_progress);
if (result.ec != std::errc{} || result.ptr != progress.data() + progress.size())
return;
mc_print_percent = parsed_progress;
} else if (value.is_number_integer())
mc_print_percent = value.get<int>();
else
return;
if (BBLSubTask* curr_task = get_subtask())
curr_task->task_progress = mc_print_percent;
}
int MachineObject::connect()
int MachineObject::connect(bool use_openssl)
{
if (get_dev_ip().empty()) return -1;
std::string username = m_agent ? m_agent->default_lan_username() : std::string();
std::string username = "bblp";
std::string password = get_access_code();
std::string port;
std::string input = get_dev_ip();
const bool use_ssl = input.rfind("https", 0) == 0;
// This strips out the http/https prefix
std::string host = Http::get_host_from_url(input, &port);
std::string ca_file;
if (GUI::wxGetApp().preset_bundle) {
const auto& config = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config;
if (port.empty())
port = config.opt_string("printhost_port");
ca_file = config.opt_string("printhost_cafile");
}
if (host.empty())
host = get_dev_ip();
if (m_agent) {
try {
PrinterConnectionParams params{
get_dev_id(),
host,
port,
username,
password,
use_ssl,
ca_file
};
return m_agent->connect_printer(params);
return m_agent->connect_printer(get_dev_id(), get_dev_ip(), username, password, use_openssl);
} catch (...) {
;
}
@@ -2778,14 +2768,6 @@ int MachineObject::publish_json(const json& json_item, int qos, int flag)
BOOST_LOG_TRIVIAL(error) << "publish_json: " << json_item.dump() << " code: " << rtn;
}
// why: the agent is the only thing that knows what it can translate, so it reports
// not-supported in its return value and this - the single funnel every command_* builder
// passes through - is the one place that turns it into something the user sees. No list of
// unsupported commands is needed anywhere: an agent that has no case for a command says so.
if (rtn == ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED || rtn == ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE) {
show_unsupported_dlg(rtn);
}
return rtn;
}
@@ -3104,13 +3086,6 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
}
} catch (...) {}
try {
if (j.contains("info"))
parse_new_info2(j["info"]);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "parse_json: failed to parse OrcaSonar capability info";
}
try {
if (auto ptr = m_fila_system->GetAmsFirmwareSwitch().lock()) {
ptr->ParseFirmwareSwitch(j);
@@ -3363,7 +3338,10 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
print_type = jj["print_type"].get<std::string>();
}
if (jj.contains("mc_percent")) {
update_print_progress(jj["mc_percent"]);
if (jj["mc_percent"].is_string())
mc_print_percent = stoi(j["print"]["mc_percent"].get<std::string>());
else if (jj["mc_percent"].is_number_integer())
mc_print_percent = j["print"]["mc_percent"].get<int>();
}
if (jj.contains("mc_print_sub_stage")) {
if (jj["mc_print_sub_stage"].is_number_integer())
@@ -3533,9 +3511,6 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
this->task_id_ = jj["task_id"].get<std::string>();
}
if (jj.contains("thumbnail_url") && jj["thumbnail_url"].is_string())
m_agent_thumbnail_url = jj["thumbnail_url"].get<std::string>();
if (jj.contains("job_attr")) {
int jobAttr = jj["job_attr"].get<int>();
jobState_ = get_flag_bits(jobAttr, 4, 4);
@@ -3581,6 +3556,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
update_slice_info(jj["project_id"].get<std::string>(), jj["profile_id"].get<std::string>(), jj["subtask_id"].get<std::string>(), plate_index);
BBLSubTask* curr_task = get_subtask();
if (curr_task) {
curr_task->task_progress = mc_print_percent;
curr_task->printing_status = print_status;
curr_task->task_id = jj["subtask_id"].get<std::string>();
}
@@ -3883,7 +3859,6 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
has_ipcam = true;
} else {
has_ipcam = false;
webcam_stream_url.clear();
}
}
if (ipcam.contains("resolution")) {
@@ -3918,9 +3893,6 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
liveview_local = local_rtsp_url.empty() ? LVL_None : local_rtsp_url == "disable"
? LVL_Disable : boost::algorithm::starts_with(local_rtsp_url, "rtsps") ? LVL_Rtsps : LVL_Rtsp;
}
if (ipcam.contains("stream_url") && ipcam["stream_url"].is_string()) {
webcam_stream_url = ipcam["stream_url"].get<std::string>();
}
if (ipcam.contains("tutk_server")) {
tutk_state = ipcam["tutk_server"].get<std::string>();
}
@@ -4679,7 +4651,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
if (diff.count() > 10.0f) {
BOOST_LOG_TRIVIAL(trace) << "parse_json timeout = " << diff.count();
}
DeviceManager::update_local_machine(*this, m_manager ? m_manager->get_app_config() : GUI::wxGetApp().app_config);
DeviceManager::update_local_machine(*this);
return 0;
}
@@ -5488,86 +5460,6 @@ void MachineObject::parse_new_info(json print)
}
}
void MachineObject::parse_new_info2(const json& info)
{
if (!info.is_object() || info.value("command", "") != "get_capabilities")
return;
const auto capabilities_it = info.find("capabilities");
if (capabilities_it == info.end() || !capabilities_it->is_object())
return;
const auto flags_it = capabilities_it->find("flags");
if (flags_it == capabilities_it->end() || !flags_it->is_object())
return;
const json& flags = *flags_it;
BOOST_LOG_TRIVIAL(info) << "parse_new_info2: OrcaSonar capability flags=" << flags.dump();
auto parse_bool = [&flags](const char* name, bool& target) {
const auto it = flags.find(name);
if (it != flags.end() && it->is_boolean())
target = it->get<bool>();
};
parse_bool("support_send_to_sd", is_support_send_to_sdcard);
parse_bool("support_filament_backup", is_support_filament_backup);
parse_bool("support_update_remain", is_support_update_remain);
parse_bool("support_auto_recovery_step_loss", is_support_auto_recovery_step_loss);
parse_bool("support_ams_humidity", is_support_ams_humidity);
parse_bool("support_prompt_sound", is_support_prompt_sound);
parse_bool("support_filament_tangle_detect", is_support_filament_tangle_detect);
parse_bool("support_1080dpi", is_support_1080dpi);
parse_bool("support_cloud_print_only", is_support_cloud_print_only);
parse_bool("support_command_ams_switch", is_support_command_ams_switch);
parse_bool("support_mqtt_alive", is_support_mqtt_alive);
parse_bool("support_motor_noise_cali", is_support_motor_noise_cali);
parse_bool("support_timelapse", is_support_timelapse);
parse_bool("support_user_preset", is_support_user_preset);
parse_bool("support_refresh_nozzle", is_support_refresh_nozzle);
parse_bool("support_flow_calibration", is_support_flow_calibration);
parse_bool("support_build_plate_marker_detect", is_support_build_plate_marker_detect);
parse_bool("support_nozzle_blob_detect", is_support_nozzle_blob_detection);
if (!m_manager->IsMultiMachineEnabled() && !is_support_agora)
parse_bool("support_tunnel_mqtt", is_support_tunnel_mqtt);
const auto bed_leveling_it = flags.find("support_bed_leveling");
if (bed_leveling_it != flags.end() && bed_leveling_it->is_number_integer())
is_support_bed_leveling = bed_leveling_it->get<int>();
auto copy_bool = [&flags](json& target, const char* name) {
const auto it = flags.find(name);
if (it != flags.end() && it->is_boolean())
target[name] = *it;
};
// The capability manifest uses an object for this range, while the legacy
// DeviceCore parser consumes a boolean plus a two-element range array.
json device_config;
copy_bool(device_config, "support_chamber");
copy_bool(device_config, "support_first_layer_inspect");
copy_bool(device_config, "support_ai_monitoring");
copy_bool(device_config, "support_lidar_calibration");
const auto chamber_edit_it = flags.find("support_chamber_temp_edit");
if (chamber_edit_it != flags.end() && chamber_edit_it->is_boolean()) {
device_config["support_chamber_temp_edit"] = *chamber_edit_it;
} else if (chamber_edit_it != flags.end() && chamber_edit_it->is_object()) {
const auto min_it = chamber_edit_it->find("min");
const auto max_it = chamber_edit_it->find("max");
if (min_it != chamber_edit_it->end() && max_it != chamber_edit_it->end() && min_it->is_number() && max_it->is_number()) {
device_config["support_chamber_temp_edit"] = true;
device_config["support_chamber_temp_edit_range"] = {*min_it, *max_it};
}
}
json fan_config;
copy_bool(fan_config, "support_aux_fan");
copy_bool(fan_config, "support_chamber_fan");
m_config->ParseConfig(device_config);
m_fan->ParseV2_0(fan_config);
}
static bool is_hex_digit(char c) {
return std::isxdigit(static_cast<unsigned char>(c)) != 0;
}
@@ -6073,15 +5965,6 @@ bool MachineObject::HasAms() const
return m_fila_system->HasAms();
}
int MachineObject::command_with_dialog(int cmd_result)
{
if (!m_agent)
return -1;
if (cmd_result == ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED || cmd_result == ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE)
show_unsupported_dlg(cmd_result);
return cmd_result;
}
void change_the_opacity(wxColour& colour)
{
if (colour.Alpha() == 255) {
+29 -15
View File
@@ -18,7 +18,6 @@
#include "boost/bimap/bimap.hpp"
#include "libslic3r/calib.hpp"
#include "libslic3r/Utils.hpp"
#include "slic3r/Utils/PrinterNetworkTypes.hpp"
#include "DeviceCore/DevDefs.h"
#include "DeviceCore/DevConfigUtil.h"
@@ -101,6 +100,7 @@ struct DevPrintTaskRatingInfo;
// given nozzle diameter (mm), bucketed per nozzle size to mirror the printer firmware.
bool is_stringing_prone_filament(const std::string& filament_id, float nozzle_diameter);
class MachineObject
{
private:
@@ -307,7 +307,7 @@ public:
bool ams_support_virtual_tray { true };
time_t ams_user_setting_start = 0;
time_t ams_switch_filament_start = 0;
AmsStatusMain ams_status_main = AmsStatusMain::AMS_STATUS_MAIN_IDLE;
AmsStatusMain ams_status_main;
int ams_status_sub;
int ams_version = 0;
@@ -547,12 +547,30 @@ public:
bool xcam_first_layer_inspector { false };
time_t xcam_first_layer_hold_start = 0;
std::string local_rtsp_url;
std::string webcam_stream_url;
std::string tutk_state;
LiveviewLocal liveview_local{ LiveviewLocal::LVL_None };
LiveviewRemote liveview_remote{ LiveviewRemote::LVR_None};
FileLocal file_local{ FileLocal::FL_None };
FileRemote file_remote{ FileRemote::FR_None };
enum LiveviewLocal {
LVL_None,
LVL_Disable,
LVL_Local,
LVL_Rtsps,
LVL_Rtsp
} liveview_local{ LVL_None };
enum LiveviewRemote {
LVR_None,
LVR_Tutk,
LVR_Agora,
LVR_TutkAgora
} liveview_remote{ LVR_None };
enum FileLocal {
FL_None,
FL_Local
} file_local{ FL_None };
enum FileRemote {
FR_None,
FR_Tutk,
FR_Agora,
FR_TutkAgora
} file_remote{ FR_None };
enum PlateMakerDectect : int
{
@@ -678,7 +696,7 @@ public:
/* machine mqtt apis */
int connect();
int connect(bool use_openssl = true);
int disconnect();
json_diff print_json;
@@ -690,8 +708,6 @@ public:
std::string subtask_id_;
std::string job_id_;
std::string last_subtask_id_;
// note: printer-agent-supplied thumbnail url, empty when the agent supplies none.
std::string m_agent_thumbnail_url;
BBLSliceInfo* slice_info {nullptr};
boost::thread* get_slice_info_thread { nullptr };
boost::thread* get_model_task_thread { nullptr };
@@ -749,7 +765,6 @@ public:
int command_set_printer_nozzle(std::string nozzle_type, float diameter);
int command_set_printer_nozzle2(int id, std::string nozzle_type, float diameter);
int command_get_access_code();
int command_start_camera();
int command_ack_proceed(json& proceed);
int command_purification_disable();
int command_dont_remind_next_time(json& mqtt_guard_json);
@@ -796,7 +811,8 @@ public:
int command_ams_calibrate(int ams_id);
int command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max);
int command_ams_select_tray(std::string tray_id);
int command_ams_refresh_rfid(int ams_id, int slot_id);
int command_ams_refresh_rfid(std::string tray_id);
int command_ams_refresh_rfid2(int ams_id, int slot_id);
int command_ams_control(std::string action);
int command_ams_drying_stop();
int command_start_extrusion_cali(int tray_index, int nozzle_temp, int bed_temp, float max_volumetric_speed, std::string setting_id = "");
@@ -878,7 +894,6 @@ public:
static bool is_in_printing_status(std::string status);
void set_print_state(std::string status);
void update_print_progress(const json& value);
bool is_connected();
bool is_connecting();
@@ -947,7 +962,6 @@ public:
/*for parse new info*/
bool check_enable_np(const json& print) const;
void parse_new_info(json print);
void parse_new_info2(const json& info);
int get_flag_bits(std::string str, int start, int count = 1) const;
uint32_t get_flag_bits_no_border(std::string str, int start_idx, int count = 1) const;
int get_flag_bits(int num, int start, int count = 1, int base = 10) const;
@@ -976,7 +990,7 @@ public:
void command_set_save_remote_print_file_to_storage(bool save);
private:
int command_with_dialog(int cmd_result);
/* xcam door open check*/
bool is_support_door_open_check = false;
DoorOpenCheckState xcam_door_open_check = DoorOpenCheckState::DOOR_OPEN_CHECK_DISABLE;
+146 -1
View File
@@ -1267,6 +1267,8 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
if (current_top_layer_only != required_top_layer_only)
m_viewer.toggle_top_layer_only_view_range();
read_reduced_detail_preferences();
// ORCA: darken the layers the preview layer slider is not scrubbed to
m_viewer.set_dim_previous_layers(get_app_config()->get_bool("preview_dim_previous_layers"));
m_viewer.set_dim_previous_layers_brightness(0.01f * std::stoi(get_app_config()->get("preview_dim_previous_layers_brightness")));
@@ -1491,6 +1493,10 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
// BBS: data for rendering color arrangement recommendation
m_nozzle_nums = print.config().option<ConfigOptionFloats>("nozzle_diameter")->values.size();
// the shell drag mode hides the infill only where the profile covers it: with no top or bottom
// shell, or no walls, the infill is the surface. Per-object settings are not consulted.
const PrintRegionConfig& region = print.default_region_config();
m_viewer.set_reduced_detail_hide_infill(region.top_shell_layers.value > 0 && region.bottom_shell_layers.value > 0 && region.wall_loops.value > 0);
// Orca hack: Hide filament group for non-bbl printers
if (!print.is_BBL_printer()) m_nozzle_nums = 1;
std::vector<int> filament_maps = print.get_filament_maps();
@@ -1661,6 +1667,7 @@ void GCodeViewer::reset_shell()
{
m_shells.volumes.clear();
m_shells.print_id = -1;
m_shells.with_wipe_tower = false;
m_shell_bounding_box = BoundingBoxf3();
}
@@ -1697,7 +1704,12 @@ void GCodeViewer::reset()
void GCodeViewer::render_scene(int canvas_width, int canvas_height)
{
glsafe(::glEnable(GL_DEPTH_TEST));
render_shells(canvas_width, canvas_height);
// while dragging in the solid model mode, the objects stand in for their toolpaths, cut to the
// visible layer range; the toolpath set then holds only the range's bottom and top layers
if (m_viewer.is_reduced_detail() && solid_model_enabled())
render_solid_model(canvas_width, canvas_height);
else
render_shells(canvas_width, canvas_height);
if (m_viewer.get_extrusion_roles_count() == 0)
return;
@@ -2020,6 +2032,56 @@ void GCodeViewer::update_layers_slider_mode()
// TODO m_layers_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder);
}
void GCodeViewer::set_interacting(bool interacting)
{
// with no shells to stand in for the toolpaths, the solid model would leave only the end layers
const bool usable = !solid_model_enabled() || !m_shells.volumes.empty();
m_viewer.set_reduced_detail(interacting && usable);
}
void GCodeViewer::read_reduced_detail_preferences()
{
m_reduced_detail_mode = reduced_detail_mode_from_string(get_app_config()->get("preview_reduced_detail_mode"));
apply_reduced_detail_settings();
}
void GCodeViewer::apply_reduced_detail_settings()
{
m_viewer.set_reduced_detail_mode(m_reduced_detail_mode);
}
void GCodeViewer::set_reduced_detail_mode(const std::string& mode)
{
const bool was_solid = solid_model_enabled();
m_reduced_detail_mode = reduced_detail_mode_from_string(mode);
apply_reduced_detail_settings();
reload_shells_if_solid_model_changed(was_solid);
}
libvgcode::EReducedDetailMode GCodeViewer::reduced_detail_mode_from_string(const std::string& mode)
{
if (mode == "solid")
return libvgcode::EReducedDetailMode::EndLayersOnly;
if (mode == "shell")
return libvgcode::EReducedDetailMode::ShellOnly;
return libvgcode::EReducedDetailMode::Off;
}
void GCodeViewer::reload_shells_if_solid_model_changed(bool was_enabled)
{
if (was_enabled == solid_model_enabled() || m_shells.print_id == -1)
return;
// only the prime tower comes and goes with the mode: a full reload would drop the shells
// whenever the print has moved on since they were loaded, leaving the solid model nothing to draw
if (wxGetApp().plater() == nullptr)
return;
// the shells are loaded from the current plate's print, which is not the plater's own
const Print& print = wxGetApp().plater()->get_partplate_list().get_current_fff_print();
if (static_cast<int>(print.id().id) != m_shells.print_id)
return;
update_shell_wipe_tower(print, m_gl_data_initialized);
}
void GCodeViewer::set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range)
{
m_viewer.set_layers_view_range(static_cast<uint32_t>(layers_z_range[0]), static_cast<uint32_t>(layers_z_range[1]));
@@ -2344,7 +2406,11 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const
void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_previewing)
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": initialized=%1%, force_previewing=%2%")%initialized %force_previewing;
// the shells can load before the first G-code does, so the preferences are read here as well
read_reduced_detail_preferences();
if ((print.id().id == m_shells.print_id)&&(print.get_modified_count() == m_shells.print_modify_count)) {
// the prime tower comes and goes on its own, without reloading the objects
update_shell_wipe_tower(print, initialized);
//BBS: update force previewing logic
if (force_previewing)
m_shells.previewing = force_previewing;
@@ -2453,10 +2519,45 @@ void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_p
m_shells.print_id = print.id().id;
m_shells.print_modify_count = print.get_modified_count();
m_shells.previewing = true;
update_shell_wipe_tower(print, initialized);
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": shell loaded, id change to %1%, modify_count %2%, object count %3%, glvolume count %4%")
% m_shells.print_id % m_shells.print_modify_count % object_count %m_shells.volumes.volumes.size();
}
// The prime tower as it was sliced, so that the solid model shows what the print shows. It keeps its
// opaque colour, so it never appears among the translucent shells, and stays out of their bounding box.
void GCodeViewer::update_shell_wipe_tower(const Print& print, bool initialized)
{
const bool with_wipe_tower = solid_model_enabled() && print.is_step_done(psWipeTower) && print.wipe_tower_data().wipe_tower_mesh_data;
if (with_wipe_tower == m_shells.with_wipe_tower)
return;
m_shells.with_wipe_tower = with_wipe_tower;
GLVolumePtrs& volumes = m_shells.volumes.volumes;
if (!with_wipe_tower) {
volumes.erase(std::remove_if(volumes.begin(), volumes.end(), [](GLVolume* volume) {
if (!volume->is_wipe_tower)
return false;
delete volume;
return true;
}), volumes.end());
return;
}
const PrintConfig& config = print.config();
const int plate_idx = print.get_plate_index();
const Vec3d plate_origin = print.get_plate_origin();
const float x = static_cast<float>(config.wipe_tower_x.get_at(plate_idx) + plate_origin.x());
const float y = static_cast<float>(config.wipe_tower_y.get_at(plate_idx) + plate_origin.y());
const size_t first_new = volumes.size();
m_shells.volumes.load_real_wipe_tower_preview(1000 + plate_idx, x, y, print.wipe_tower_data().wipe_tower_mesh_data->real_wipe_tower_mesh,
print.wipe_tower_data().wipe_tower_mesh_data->real_brim_mesh, true,
static_cast<float>(config.wipe_tower_rotation_angle), false, initialized);
for (size_t i = first_new; i < volumes.size(); ++i) {
volumes[i]->zoom_to_volumes = false;
volumes[i]->force_native_color = true;
volumes[i]->set_render_color();
}
}
void GCodeViewer::render_toolpaths()
{
const Camera& camera = wxGetApp().plater()->get_camera();
@@ -2657,6 +2758,50 @@ void GCodeViewer::render_shells(int canvas_width, int canvas_height)
glsafe(::glDepthMask(GL_TRUE));
}
// The sliced objects and the prime tower drawn opaque, in their filament colours, cut to the
// visible layer range by the shader's z range. The toolpaths of the range's bottom and top layers
// are drawn afterwards and cap the cut.
void GCodeViewer::render_solid_model(int canvas_width, int canvas_height)
{
if (m_shells.volumes.empty())
return;
// gouraud_light has no z range, so it could not cut the model
GLShaderProgram* shader = wxGetApp().get_shader("gouraud");
if (shader == nullptr)
return;
const libvgcode::Interval& layers = m_viewer.get_layers_view_range();
const float z_top = m_viewer.get_layer_z(layers[1]) - m_z_offset + 0.001f;
const float z_bottom = (layers[0] > 0) ? m_viewer.get_layer_z(layers[0] - 1) - m_z_offset - 0.001f : -FLT_MAX;
std::vector<float> alphas;
alphas.reserve(m_shells.volumes.volumes.size());
for (GLVolume* volume : m_shells.volumes.volumes) {
alphas.push_back(volume->color.a());
volume->color.a(1.0f);
volume->set_render_color();
}
m_shells.volumes.set_z_range(z_bottom, z_top);
// gouraud also clips by this plane, which nothing else sets on the shells
m_shells.volumes.set_clipping_plane(ClippingPlane::ClipsNothing().get_data());
shader->start_using();
// the 3D view leaves its shadow settings on the shared program
shader->set_uniform("shadow_intensity", 0.0f);
const Camera& camera = wxGetApp().plater()->get_camera();
shader->set_uniform("z_far", camera.get_far_z());
shader->set_uniform("z_near", camera.get_near_z());
m_shells.volumes.render(GLVolumeCollection::ERenderType::Opaque, false, camera.get_view_matrix(), camera.get_projection_matrix(), {canvas_width, canvas_height});
shader->stop_using();
m_shells.volumes.set_z_range(-FLT_MAX, FLT_MAX);
size_t k = 0;
for (GLVolume* volume : m_shells.volumes.volumes) {
volume->color.a(alphas[k++]);
volume->set_render_color();
}
}
//BBS
void GCodeViewer::render_all_plates_stats(const std::vector<const GCodeProcessorResult*>& gcode_result_list, bool show /*= true*/) const {
if (!show)
+22 -1
View File
@@ -174,6 +174,8 @@ public:
int print_id{-1};
int print_modify_count{-1};
bool previewing{false};
// the prime tower was loaded with the objects, for the solid model
bool with_wipe_tower{false};
};
//BBS
ConflictResultOpt m_conflict_result;
@@ -234,6 +236,17 @@ private:
bool m_legend_visible{ true };
bool m_legend_enabled{ true };
// the reduced-detail preferences, pushed to libvgcode by apply_reduced_detail_settings()
libvgcode::EReducedDetailMode m_reduced_detail_mode{ libvgcode::EReducedDetailMode::Off };
void read_reduced_detail_preferences();
void apply_reduced_detail_settings();
static libvgcode::EReducedDetailMode reduced_detail_mode_from_string(const std::string& mode);
// in the solid model mode, the sliced objects are drawn as solid shapes instead of toolpaths
bool solid_model_enabled() const { return m_reduced_detail_mode == libvgcode::EReducedDetailMode::EndLayersOnly; }
void render_solid_model(int canvas_width, int canvas_height);
// the prime tower is only among the shells for the solid model, so it is added or removed when that changes
void reload_shells_if_solid_model_changed(bool was_enabled);
void update_shell_wipe_tower(const Print& print, bool initialized);
float m_legend_height;
PrintEstimatedStatistics m_print_statistics;
@@ -291,7 +304,7 @@ public:
// void _render_calibration_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
// void _render_calibration_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
// void render_calibration_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
bool has_data() const { return !m_viewer.get_extrusion_roles().empty(); }
bool has_data() const { return m_viewer.get_extrusion_roles_count() != 0; }
bool can_export_toolpaths() const;
std::vector<int> get_plater_extruder();
@@ -363,6 +376,14 @@ public:
void set_dim_previous_layers_brightness(float value) { m_viewer.set_dim_previous_layers_brightness(value); }
float get_dim_previous_layers_brightness() const { return m_viewer.get_dim_previous_layers_brightness(); }
// whether the mouse is holding either slider's handle
bool is_slider_dragging() const { return m_layers_slider->is_dragging() || m_moves_slider->is_dragging(); }
// while the user drags the camera or a slider, draw the reduced set, if the preference asks for one
void set_interacting(bool interacting);
bool is_reduced_detail() const { return m_viewer.is_reduced_detail(); }
// the preference's string value: "off", "solid" or "shell"
void set_reduced_detail_mode(const std::string& mode);
void set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range);
bool is_legend_shown() const { return m_legend_visible && m_legend_enabled; }
+66 -9
View File
@@ -2084,6 +2084,11 @@ void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
const bool overlay_tick = m_fps_overlay_tick;
m_fps_overlay_tick = false;
// Whether the preview draws its reduced set is decided before the cached scene is consulted,
// since switching changes what the scene pass draws.
if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview && m_gcode_viewer.has_data() && _update_preview_interaction())
scene_dirty = true;
// An overlay-only frame reuses the last scene pass. The overlay is rebuilt either way, and drawn
// below once it is known whether the frame differs from the one on screen.
const bool reuse_scene = !scene_dirty && _can_reuse_cached_scene(camera);
@@ -3277,9 +3282,16 @@ void GLCanvas3D::bind_event_handlers()
if (m_selection_edit.kind != SelectionEdit::None)
finish_selection_edit();
ImGui::SetWindowFocus(nullptr);
// a drag cut short never sees its button release, which would leave the reduced set drawn
if (m_canvas_type == CanvasPreview && m_mouse.dragging && m_gcode_viewer.is_reduced_detail())
mouse_up_cleanup();
render();
evt.Skip();
});
m_canvas->Bind(wxEVT_MOUSE_CAPTURE_LOST, [this](wxMouseCaptureLostEvent&) {
if (m_canvas_type == CanvasPreview && m_mouse.dragging && m_gcode_viewer.is_reduced_detail())
mouse_up_cleanup();
});
m_event_handlers_bound = true;
m_canvas->Bind(wxEVT_GESTURE_PAN, &GLCanvas3D::on_gesture, this);
@@ -3355,6 +3367,17 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
m_overlay_dirty |= imgui_requires_extra_frame;
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_dirty |= GLTexture::Compressor::has_compressed_texture_to_refresh();
// the render timer only wakes the idle loop; the frame that puts the preview's toolpaths back
// after a wheel burst has to be asked for here, once the settle time is really up
if (m_preview_settle_pending) {
const auto now = std::chrono::steady_clock::now();
if (now >= m_preview_interaction_until) {
m_preview_settle_pending = false;
m_dirty = true;
}
else // the timer fired early
schedule_extra_frame(static_cast<int>(std::chrono::duration_cast<std::chrono::milliseconds>(m_preview_interaction_until - now).count()) + 1);
}
if (!m_dirty && !m_overlay_dirty)
return;
@@ -3885,6 +3908,10 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
return;
}
// only a wheel the panels did not take moves the camera
if (m_canvas_type == CanvasPreview)
note_preview_interaction();
#ifdef __WXMSW__
// For some reason the Idle event is not being generated after the mouse scroll event in case of scrolling with the two fingers on the touch pad,
// if the event is not allowed to be passed further.
@@ -3985,6 +4012,11 @@ void GLCanvas3D::on_fps_overlay_timer(wxTimerEvent& evt)
wxWakeUpIdle();
}
void GLCanvas3D::note_preview_interaction()
{
m_preview_interaction_until = std::chrono::steady_clock::now() + std::chrono::milliseconds(150);
}
void GLCanvas3D::schedule_extra_frame(int milliseconds)
{
// Schedule idle event right now
@@ -5631,6 +5663,9 @@ void GLCanvas3D::mouse_up_cleanup()
m_mouse.ignore_left_up = false;
m_mouse.ignore_right_up = false;
m_dirty = true;
// the frame that follows a release puts the preview's toolpaths back, and on some platforms
// no idle event follows a button release until the next input
wxWakeUpIdle();
if (m_canvas->HasCapture())
m_canvas->ReleaseMouse();
@@ -6546,11 +6581,8 @@ void GLCanvas3D::render_thumbnail_internal(ThumbnailData& thumbnail_data, const
// glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f));
BOOST_LOG_TRIVIAL(info) << boost::format("render_thumbnail: finished");
// Puts the canvas viewport back in place of the thumbnail one set above. The CLI renders
// thumbnails with no application and no plater, so there is no canvas viewport to restore.
if (wxTheApp != nullptr)
if (Plater *plater = wxGetApp().plater(); plater != nullptr)
plater->get_camera().apply_viewport();
// Puts the canvas viewport back in place of the thumbnail one set above.
wxGetApp().plater()->get_camera().apply_viewport();
}
void GLCanvas3D::render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params,
@@ -7782,13 +7814,20 @@ bool GLCanvas3D::_is_scene_cacheable() const
return false;
#endif
// The scene follows the cursor during a drag, under a gizmo that draws at the cursor, and while
// the cursor is on the layer height bar, where the object shader draws a band at its height.
// The scene follows the cursor while the user drags, under a gizmo that draws at the cursor, and
// while the cursor is on the layer height bar, where the object shader draws a band at its height.
const GLGizmoBase* gizmo = m_gizmos.get_current();
const bool cursor_on_layers_bar = is_layers_editing_enabled() &&
m_layers_editing.bar_rect_contains(*this, (float)m_mouse.position.x(), (float)m_mouse.position.y());
return !m_mouse.dragging && !m_gizmos.is_dragging() && !m_rectangle_selection.is_dragging() &&
(gizmo == nullptr || !gizmo->render_follows_cursor()) && !cursor_on_layers_bar;
return !is_user_interacting() && (gizmo == nullptr || !gizmo->render_follows_cursor()) && !cursor_on_layers_bar;
}
// Whether the user is holding something that moves the scene: the camera, the navigator, a gizmo,
// the rectangle selection or a preview slider.
bool GLCanvas3D::is_user_interacting() const
{
return m_mouse.dragging || m_navigator_dragging || m_gizmos.is_dragging() || m_rectangle_selection.is_dragging() ||
m_gcode_viewer.is_slider_dragging();
}
bool GLCanvas3D::_is_frame_skipping_enabled() const
@@ -8779,6 +8818,24 @@ void GLCanvas3D::_render_wireframe_overlay()
shader->stop_using();
}
// The reduced set is drawn while the camera, the navigator or either slider is dragged. A wheel
// step has no duration, so it holds the reduced set for a settle time instead, and the frame that
// restores the full toolpaths is scheduled for when that time runs out. Returns whether what the scene
// pass draws changed, since a frame that reuses the cached scene would hide the change.
bool GLCanvas3D::_update_preview_interaction()
{
const auto now = std::chrono::steady_clock::now();
const bool settling = now < m_preview_interaction_until;
const bool dragging = is_user_interacting();
const bool was_reduced = m_gcode_viewer.is_reduced_detail();
m_gcode_viewer.set_interacting(dragging || settling);
if (settling && !dragging && m_gcode_viewer.is_reduced_detail()) {
m_preview_settle_pending = true;
schedule_extra_frame(static_cast<int>(std::chrono::duration_cast<std::chrono::milliseconds>(m_preview_interaction_until - now).count()) + 1);
}
return m_gcode_viewer.is_reduced_detail() != was_reduced;
}
//BBS: GUI refactor: add canvas size as parameters
void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height)
{
+11
View File
@@ -656,6 +656,10 @@ private:
ECursorType m_cursor_type;
GLSelectionRectangle m_rectangle_selection;
bool m_navigator_dragging{ false };
// until when a wheel step keeps the preview's reduced set drawn
std::chrono::time_point<std::chrono::steady_clock> m_preview_interaction_until{};
// whether the frame that restores the toolpaths once that time is up is still owed
bool m_preview_settle_pending{ false };
//BBS:add plate related logic
mutable std::vector<int> m_hover_volume_idxs;
@@ -1222,6 +1226,10 @@ public:
void msw_rescale() { m_gcode_viewer.invalidate_legend(); }
void request_extra_frame() { m_extra_frame_requested = true; }
// whether the user is holding the camera, the navigator, a gizmo, the rectangle selection or a preview slider
bool is_user_interacting() const;
// a wheel step is over before the next frame, so it holds the preview's reduced set for a settle time
void note_preview_interaction();
void schedule_extra_frame(int milliseconds);
@@ -1372,6 +1380,9 @@ private:
//BBS: GUI refactor: add canvas size as parameters
void _render_gcode(int canvas_width, int canvas_height);
void _render_gcode_overlay(int canvas_width, int canvas_height);
// decides whether the preview draws its reduced set this frame and returns whether what the scene
// pass draws changed; runs before the cached scene is consulted
bool _update_preview_interaction();
//BBS: render a plane for assemble
void _render_plane() const;
void _render_selection();
-32
View File
@@ -14,9 +14,6 @@
#include <boost/algorithm/string/predicate.hpp>
#include <boost/any.hpp>
#include <wx/filename.h>
#include <wx/filesys.h>
#if __APPLE__
#import <IOKit/pwr_mgt/IOPMLib.h>
#elif _WIN32
@@ -30,13 +27,11 @@
#include "AboutDialog.hpp"
#include "MsgDialog.hpp"
#include "Plater.hpp"
#include "format.hpp"
#include "WebUserLoginDialog.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/Utils.hpp"
namespace Slic3r {
@@ -536,11 +531,6 @@ boost::filesystem::path into_path(const wxString &str)
return boost::filesystem::path(str.wx_str());
}
wxString file_url_from_path(const boost::filesystem::path &path)
{
return wxFileSystem::FileNameToURL(wxFileName(from_path(path)));
}
void about()
{
AboutDialog dlg;
@@ -645,26 +635,4 @@ void desktop_open_any_folder( const std::string& path )
}
bool desktop_open_project_attachment(wxWindow *parent, const boost::filesystem::path &path)
{
// The auxiliary path is UTF-8, which is what boost::filesystem reads a narrow string as.
const boost::filesystem::path aux_root(wxGetApp().plater()->model().get_auxiliary_file_temp_path());
boost::system::error_code ec;
if (!is_absolute_path_within_root(path, aux_root) || !boost::filesystem::is_regular_file(path, ec))
return false;
// Attachments come with the project and carry no download mark, so the desktop would open them without a warning.
if (!is_safe_to_open_file_name(path.filename().string())) {
MessageDialog dlg(parent,
wxString::Format(_L("\"%s\" is not a plain document, image or model file. Opening it may run it as a "
"program or script on this computer.\n\n"
"Only open attachments from projects you trust. Open it anyway?"),
from_path(path.filename())),
_L("Open attachment"), wxICON_WARNING | wxYES_NO);
if (dlg.ShowModal() != wxID_YES)
return false;
}
return wxLaunchDefaultApplication(from_path(path), 0);
}
} }
-6
View File
@@ -76,9 +76,6 @@ std::string into_u8(const wxString &str);
wxString from_path(const boost::filesystem::path &path);
// boost path from wxString
boost::filesystem::path into_path(const wxString &str);
// file:// URL of a local path, percent-encoded so characters such as '#', '%' and '?' stay part of the path.
// Append any query or fragment to the result.
wxString file_url_from_path(const boost::filesystem::path &path);
// Display an About dialog
extern void about();
@@ -86,9 +83,6 @@ extern void about();
extern void desktop_open_datadir_folder();
// Ask the destop to open one folder
extern void desktop_open_any_folder(const std::string& path);
// Ask the desktop to open a file from the project's auxiliary directory, after a confirmation
// unless its type is known to be plain content. Returns false if the file was not opened.
extern bool desktop_open_project_attachment(wxWindow *parent, const boost::filesystem::path &path);
} // namespace GUI
} // namespace Slic3r
+51 -59
View File
@@ -4,7 +4,6 @@
#include "libslic3r/Platform.hpp"
#include "GUI_App.hpp"
#include "Shortcuts.hpp"
#include "DeviceCore/DevConfigUtil.h"
#include "BindDialog.hpp"
#include "DeviceManager.hpp"
#include "HMS.hpp"
@@ -25,6 +24,7 @@
#include <boost/locale/encoding_utf.hpp>
#include <boost/log/detail/native_typeof.hpp>
#include <libslic3r/Config.hpp>
#include <mutex>
#include <slic3r/plugin/PythonPluginInterface.hpp>
#include <wx/event.h>
@@ -41,11 +41,9 @@
#include <iterator>
#include <exception>
#include <cstdlib>
#include <mutex>
#include <regex>
#include <thread>
#include <string_view>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
@@ -90,6 +88,7 @@
#include "libslic3r/Thread.hpp"
#include "libslic3r/miniz_extension.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/Color.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "slic3r/plugin/host/PluginHostUi.hpp"
#include "slic3r/plugin/PythonInterpreter.hpp"
@@ -109,18 +108,19 @@
#include "../Utils/PrintHost.hpp"
#include "../Utils/Process.hpp"
#include "../Utils/wxInspectorPlugins/Registration.hpp"
#include "../Utils/MacDarkMode.hpp"
#include "../Utils/Http.hpp"
#include "../Utils/InstanceID.hpp"
#include "../Utils/UndoRedo.hpp"
#include "slic3r/Config/Snapshot.hpp"
#include "Preferences.hpp"
#include "Tab.hpp"
#include "SysInfoDialog.hpp"
#include "UpdateDialogs.hpp"
#include "Mouse3DController.hpp"
#include "RemovableDriveManager.hpp"
#include "InstanceCheck.hpp"
#ifdef __APPLE__
#include "../Utils/MacDarkMode.hpp"
#include "DeepLinkHandlerMac.h"
#endif
#include "NotificationManager.hpp"
@@ -129,6 +129,8 @@
#include "PrintHostDialogs.hpp"
#include "NetworkPluginDialog.hpp"
#include "DesktopIntegrationDialog.hpp"
#include "SendSystemInfoDialog.hpp"
#include "ParamsDialog.hpp"
#include "KBShortcutsDialog.hpp"
#include "DownloadProgressDialog.hpp"
#include "TroubleshootDialog.hpp"
@@ -139,6 +141,7 @@
#include "Widgets/ProgressDialog.hpp"
//BBS: DailyTip and UserGuide Dialog
#include "WebDownPluginDlg.hpp"
#include "WebGuideDialog.hpp"
#include "ReleaseNote.hpp"
#include "PrivacyUpdateDialog.hpp"
@@ -1509,33 +1512,11 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP
size_t n = mz_zip_reader_get_extra(&archive, stat.m_file_index, extra.data(), extra.size());
dest_file = decode(extra.substr(0, n), stat.m_filename);
}
if (!is_path_within_root(dest_file, plugin_folder)) {
BOOST_LOG_TRIVIAL(error) << "[install_plugin] entry " << dest_file << " resolves outside " << plugin_folder.string();
close_zip_reader(&archive);
if (pro_fn) { pro_fn(InstallStatusUnzipFailed, 0, cancel); }
return InstallStatusUnzipFailed;
}
auto dest_path = plugin_folder / dest_file;
boost::filesystem::create_directories(dest_path.parent_path());
std::string dest_zip_file = encode_path(dest_path.string().c_str());
#ifndef WIN32
// Validate a symlink's target before anything at the destination is replaced.
const bool is_link = S_ISLNK(stat.m_external_attr >> 16);
std::string link;
if (is_link) {
link.assign(stat.m_uncomp_size, 0);
if (!mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, link.data(), stat.m_uncomp_size, 0) ||
!is_symlink_target_within_root(dest_file, link, plugin_folder)) {
BOOST_LOG_TRIVIAL(error) << "[install_plugin] link " << dest_file << " -> " << link << " is unreadable or resolves outside " << plugin_folder.string();
close_zip_reader(&archive);
if (pro_fn) { pro_fn(InstallStatusUnzipFailed, 0, cancel); }
return InstallStatusUnzipFailed;
}
}
#endif
try {
boost::filesystem::create_directories(dest_path.parent_path());
// symlink_status so that an existing symlink, dangling or not, is replaced rather than written through.
if (fs::exists(fs::symlink_status(dest_path))) {
if (fs::exists(dest_path)) {
boost::system::error_code ec;
fs::remove(dest_path, ec);
if (ec) {
@@ -1563,8 +1544,9 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP
}
mz_bool res = 0;
#ifndef WIN32
if (is_link) {
res = 1;
if (S_ISLNK(stat.m_external_attr >> 16)) {
std::string link(stat.m_uncomp_size + 1, 0);
res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, link.data(), stat.m_uncomp_size, 0);
try {
boost::filesystem::create_symlink(link, dest_path);
} catch (const std::exception &e) {
@@ -2416,23 +2398,34 @@ GUI_App::~GUI_App()
bool GUI_App::is_blocking_printing(MachineObject *obj_)
{
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
const std::string source_model = preset_bundle
? preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle)
: std::string();
return is_blocking_printing(obj_, source_model);
}
bool GUI_App::is_blocking_printing(MachineObject *obj_, const std::string& source_model)
{
DeviceManager *dev = getDeviceManager();
DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!dev) return true;
if (obj_ == nullptr)
std::string target_model;
if (obj_ == nullptr) {
obj_ = dev->get_selected_machine();
if (!obj_)
return false;
if (obj_) {
target_model = obj_->printer_type;
}
} else {
target_model = obj_->printer_type;
}
return !DevPrinterConfigUtil::is_printer_model_compatible(source_model, *obj_);
if (!obj_)
{
return false;
}
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
std::string source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
if (source_model != target_model) {
std::vector<std::string> compatible_machine = obj_->get_compatible_machine();
vector<std::string>::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model);
if (it == compatible_machine.end()) {
return true;
}
}
return false;
}
// If formatted for github, plaintext with OpenGL extensions enclosed into <details>.
@@ -3993,16 +3986,20 @@ void GUI_App::set_live_printer_agent(std::shared_ptr<IPrinterAgent> agent)
m_agent->set_user_selected_machine("");
// note: belt-and-suspenders (precedent: DeviceManagerRefresher::on_timer)
dev->OnSelectedMachineLost(); // why: clear stale sidebar sync-status / AMS
// why: retain agent-owned LAN discoveries so agents without automatic discovery (for
// example the Moonraker-based Qidi/Snapmaker agents) can reuse them after a switch.
dev->clear_other_devices();
// why: drop stale LAN discoveries; keep My Devices, but only those belonging to the
// agent we're about to swap to, so a device stamped by the outgoing agent doesn't
// linger hidden - the new agent's start_discovery re-inserts and re-stamps it fresh.
// agent is null when clearing the live agent entirely (e.g. plugin unload); there's no
// target to filter against then, so fall back to the original "keep all My Devices"
// behavior rather than guessing.
dev->clear_other_devices(agent ? agent->get_agent_info().id : std::string());
}
m_agent->set_printer_agent(agent);
sidebar().update_all_preset_comboboxes();
}
std::string GUI_App::resolve_printer_agent_id(const std::string& stored_id) const
std::string GUI_App::resolve_printer_agent_id(const std::string& stored_id)
{
if (!stored_id.empty())
return stored_id;
@@ -4038,7 +4035,6 @@ void GUI_App::switch_printer_agent()
std::string log_dir = data_dir();
std::string cloud_agent_id = agent_info.id == BBL_PRINTER_AGENT_ID ? BBL_CLOUD_PROVIDER : ORCA_CLOUD_PROVIDER;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << agent_info.id;
std::shared_ptr<ICloudServiceAgent> cloud_agent = m_agent->get_cloud_agent(cloud_agent_id);
// Create new printer agent via registry
@@ -4052,10 +4048,8 @@ void GUI_App::switch_printer_agent()
return;
}
// Compare the registered IDs, not only the implementation pointer. Different registry IDs
// may intentionally be backed by the same implementation object (especially for plugins).
const auto current_printer_agent = m_agent->get_printer_agent();
if (current_printer_agent && current_printer_agent->get_agent_info().id == effective_agent_id) {
// The factory caches agents per ID, so an identical pointer means the agent type is unchanged.
if (m_agent->get_printer_agent() == new_printer_agent) {
// Orca: the agent type is unchanged (e.g. switching between two Moonraker/Klipper
// printer presets), so the selected machine and the agent's cached device_info still
// point at the previously active printer preset. Re-select the machine when the new
@@ -5038,16 +5032,14 @@ bool GUI_App::is_user_login(const std::string& provider/* = ORCA_CLOUD_PROVIDER*
return false;
}
std::string GUI_App::get_printer_cloud_provider() const
const std::string& GUI_App::get_printer_cloud_provider() const
{
const std::string agent_id = resolve_printer_agent_id(
preset_bundle ? preset_bundle->printers.get_edited_preset().config.opt_string("printer_agent")
: std::string());
return agent_id == BBL_PRINTER_AGENT_ID ? BBL_CLOUD_PROVIDER : ORCA_CLOUD_PROVIDER;
// Orca todo: this need to be revisted. currently it is mainly used for device manager and related clausses and only bambu machines use them.
//
return BBL_CLOUD_PROVIDER;
}
bool GUI_App::check_login(const std::string& provider/* = ORCA_CLOUD_PROVIDER*/)
{
bool result = false;
+5 -3
View File
@@ -24,6 +24,9 @@
#include <wx/snglinst.h>
#include <wx/msgdlg.h>
#include <mutex>
#include <stack>
//#define BBL_HAS_FIRST_PAGE 1
#define STUDIO_INACTIVE_TIMEOUT 15*60*1000
#define LOG_FILES_MAX_NUM 30
@@ -370,7 +373,6 @@ public:
EAppMode get_app_mode() const { return m_app_mode; }
Slic3r::DeviceManager* getDeviceManager() { return m_device_manager; }
bool is_blocking_printing(MachineObject *obj_ = nullptr);
bool is_blocking_printing(MachineObject *obj_, const std::string& source_model);
Slic3r::TaskManager* getTaskManager() { return m_task_manager; }
HMSQuery* get_hms_query() { return hms_query; }
NetworkAgent* getAgent() { return m_agent; }
@@ -378,7 +380,7 @@ public:
// Reconcile the live printer agent with the stored preset selection.
void switch_printer_agent();
std::string resolve_printer_agent_id(const std::string& stored_id) const;
std::string resolve_printer_agent_id(const std::string& stored_id);
// ORCA TODO: in the future, bbl presets should specify "bbl" printer agent id
// then, all resolve and canonical would just be ORCA<->""
std::string canonical_printer_agent_id(const std::string& picked_id);
@@ -509,7 +511,7 @@ public:
bool check_login(const std::string& provider = ORCA_CLOUD_PROVIDER);
void get_login_info(const std::string& provider = ORCA_CLOUD_PROVIDER);
bool is_user_login(const std::string& provider = ORCA_CLOUD_PROVIDER);
std::string get_printer_cloud_provider() const;
const std::string& get_printer_cloud_provider() const;
void request_user_login(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER);
void request_user_handle(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER);
+7
View File
@@ -483,6 +483,11 @@ void IMSlider::draw_background_and_groove(const ImRect& bg_rect, const ImRect& g
ImGui::RenderFrame(groove.Min, groove.Max, groove_col, false, 0.5 * groove.GetWidth());
}
bool IMSlider::is_dragging() const
{
return GImGui != nullptr && m_imgui_id != 0 && GImGui->ActiveId == m_imgui_id && GImGui->IO.MouseDown[0];
}
bool IMSlider::horizontal_slider(const char* str_id, int* value, int v_min, int v_max, const ImVec2& size, float scale)
{
ImGuiWindow* window = ImGui::GetCurrentWindow();
@@ -491,6 +496,7 @@ bool IMSlider::horizontal_slider(const char* str_id, int* value, int v_min, int
ImGuiContext& context = *GImGui;
const ImGuiID id = window->GetID(str_id);
m_imgui_id = id;
const ImVec2 pos = window->DC.CursorPos;
const ImRect draw_region(pos, pos + size);
@@ -883,6 +889,7 @@ bool IMSlider::vertical_slider(const char* str_id, int* higher_value, int* lower
ImGuiContext& context = *GImGui;
const ImGuiID id = window->GetID(str_id);
m_imgui_id = id;
const ImVec2 pos = window->DC.CursorPos;
const ImRect draw_region(pos, pos + size);
+5
View File
@@ -118,6 +118,9 @@ public:
//BBS update scroll value changed
bool is_dirty() { return m_dirty; }
// whether the mouse is holding this slider's handle, read from ImGui's active id rather than
// from the dirty flag, which is raised and consumed inside a single frame
bool is_dragging() const;
void set_as_dirty(bool dirty = true) { m_dirty = dirty; }
bool is_need_post_tick_event() { return m_is_need_post_tick_changed_event; }
void reset_post_tick_event(bool val = false) {
@@ -182,6 +185,8 @@ private:
int m_higher_value;
int m_one_layer_value; // ORCA
bool m_dirty = false;
// the ImGui id of the slider widget, as of its last render
unsigned int m_imgui_id = 0;
bool m_render_as_disabled{ false };

Some files were not shown because too many files have changed in this diff Show More