mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-15 21:17:41 +00:00
feat: generic camera stream support for http snapshot and rtsp
This commit is contained in:
@@ -1067,6 +1067,7 @@ function(orcaslicer_copy_dlls target config postfix output_dlls)
|
|||||||
${CMAKE_PREFIX_PATH}/bin/occt/TKXDESTEP.dll
|
${CMAKE_PREFIX_PATH}/bin/occt/TKXDESTEP.dll
|
||||||
${CMAKE_PREFIX_PATH}/bin/occt/TKXSBase.dll
|
${CMAKE_PREFIX_PATH}/bin/occt/TKXSBase.dll
|
||||||
${CMAKE_PREFIX_PATH}/bin/freetype.dll
|
${CMAKE_PREFIX_PATH}/bin/freetype.dll
|
||||||
|
${CMAKE_PREFIX_PATH}/bin/avformat-61.dll
|
||||||
${CMAKE_PREFIX_PATH}/bin/avcodec-61.dll
|
${CMAKE_PREFIX_PATH}/bin/avcodec-61.dll
|
||||||
${CMAKE_PREFIX_PATH}/bin/swresample-5.dll
|
${CMAKE_PREFIX_PATH}/bin/swresample-5.dll
|
||||||
${CMAKE_PREFIX_PATH}/bin/swscale-8.dll
|
${CMAKE_PREFIX_PATH}/bin/swscale-8.dll
|
||||||
@@ -1106,6 +1107,7 @@ function(orcaslicer_copy_dlls target config postfix output_dlls)
|
|||||||
${_out_dir}/TKXSBase.dll
|
${_out_dir}/TKXSBase.dll
|
||||||
|
|
||||||
${_out_dir}/freetype.dll
|
${_out_dir}/freetype.dll
|
||||||
|
${_out_dir}/avformat-61.dll
|
||||||
${_out_dir}/avcodec-61.dll
|
${_out_dir}/avcodec-61.dll
|
||||||
${_out_dir}/swresample-5.dll
|
${_out_dir}/swresample-5.dll
|
||||||
${_out_dir}/swscale-8.dll
|
${_out_dir}/swscale-8.dll
|
||||||
@@ -1128,7 +1130,10 @@ function(orcaslicer_copy_sos target config postfix output_sos)
|
|||||||
set(_out_dir "${CMAKE_CURRENT_BINARY_DIR}")
|
set(_out_dir "${CMAKE_CURRENT_BINARY_DIR}")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
file(COPY ${CMAKE_PREFIX_PATH}/lib/libavcodec.so
|
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
|
||||||
${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61
|
${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61
|
||||||
${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61.3.100
|
${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61.3.100
|
||||||
${CMAKE_PREFIX_PATH}/lib/libavutil.so
|
${CMAKE_PREFIX_PATH}/lib/libavutil.so
|
||||||
@@ -1143,6 +1148,9 @@ function(orcaslicer_copy_sos target config postfix output_sos)
|
|||||||
DESTINATION ${_out_dir})
|
DESTINATION ${_out_dir})
|
||||||
|
|
||||||
set(${output_sos}
|
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
|
||||||
${_out_dir}/libavcodec.so.61
|
${_out_dir}/libavcodec.so.61
|
||||||
${_out_dir}/libavcodec.so.61.3.100
|
${_out_dir}/libavcodec.so.61.3.100
|
||||||
@@ -1271,6 +1279,8 @@ endif ()
|
|||||||
|
|
||||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||||
set(LIBRARY_FILES
|
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
|
||||||
${LIBDIR_BIN}/libavcodec.so.61.3.100
|
${LIBDIR_BIN}/libavcodec.so.61.3.100
|
||||||
${LIBDIR_BIN}/libavutil.so.59
|
${LIBDIR_BIN}/libavutil.so.59
|
||||||
|
|||||||
4
deps/FFMPEG/FFMPEG.cmake
vendored
4
deps/FFMPEG/FFMPEG.cmake
vendored
@@ -69,14 +69,14 @@ else ()
|
|||||||
--disable-filters
|
--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*
|
--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
|
--disable-protocols
|
||||||
--enable-protocol=file,fd,pipe,rtp,udp
|
--enable-protocol=file,fd,pipe,rtp,tcp,udp
|
||||||
--disable-muxers
|
--disable-muxers
|
||||||
--enable-muxer=rtp
|
--enable-muxer=rtp
|
||||||
--disable-encoders
|
--disable-encoders
|
||||||
--disable-decoders
|
--disable-decoders
|
||||||
--enable-decoder=*aac*,h264*,mp3*,mjpeg,rv*
|
--enable-decoder=*aac*,h264*,mp3*,mjpeg,rv*
|
||||||
--disable-demuxers
|
--disable-demuxers
|
||||||
--enable-demuxer=h264,mp3,mov
|
--enable-demuxer=h264,mp3,mov,rtsp,sdp
|
||||||
--disable-zlib
|
--disable-zlib
|
||||||
--disable-avdevice
|
--disable-avdevice
|
||||||
BUILD_IN_SOURCE ON
|
BUILD_IN_SOURCE ON
|
||||||
|
|||||||
@@ -222,6 +222,7 @@ set(SLIC3R_GUI_SOURCES
|
|||||||
GUI/GLToolbar.hpp
|
GUI/GLToolbar.hpp
|
||||||
GUI/ImageDPIFrame.cpp
|
GUI/ImageDPIFrame.cpp
|
||||||
GUI/ImageDPIFrame.hpp
|
GUI/ImageDPIFrame.hpp
|
||||||
|
GUI/IMediaController.hpp
|
||||||
GUI/GUI_App.cpp
|
GUI/GUI_App.cpp
|
||||||
GUI/GUI_App.hpp
|
GUI/GUI_App.hpp
|
||||||
GUI/GUI_AuxiliaryList.cpp
|
GUI/GUI_AuxiliaryList.cpp
|
||||||
@@ -530,6 +531,8 @@ set(SLIC3R_GUI_SOURCES
|
|||||||
GUI/WebUserLoginDialog.hpp
|
GUI/WebUserLoginDialog.hpp
|
||||||
GUI/WebViewDialog.cpp
|
GUI/WebViewDialog.cpp
|
||||||
GUI/WebViewDialog.hpp
|
GUI/WebViewDialog.hpp
|
||||||
|
GUI/WebMediaController.hpp
|
||||||
|
GUI/WebMediaController.cpp
|
||||||
GUI/Widgets/AMSControl.cpp
|
GUI/Widgets/AMSControl.cpp
|
||||||
GUI/Widgets/AMSControl.hpp
|
GUI/Widgets/AMSControl.hpp
|
||||||
GUI/Widgets/AMSItem.cpp
|
GUI/Widgets/AMSItem.cpp
|
||||||
@@ -906,14 +909,15 @@ endif ()
|
|||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
# Static FFmpeg from the deps install: nothing to bundle into the .app,
|
# Static FFmpeg from the deps install: nothing to bundle into the .app,
|
||||||
# no rpath/install_name handling. Order matters: avcodec -> swscale -> avutil.
|
# 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)
|
||||||
find_library(LIBAVCODEC_LIBRARY NAMES libavcodec.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
|
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(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)
|
find_library(LIBAVUTIL_LIBRARY NAMES libavutil.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
|
||||||
if (NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY)
|
if (NOT LIBAVFORMAT_LIBRARY OR 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.")
|
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.")
|
||||||
endif ()
|
endif ()
|
||||||
target_link_libraries(libslic3r_gui ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
|
target_link_libraries(libslic3r_gui ${LIBAVFORMAT_LIBRARY} ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
|
||||||
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include)
|
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include)
|
||||||
elseif (WIN32)
|
elseif (WIN32)
|
||||||
# Prebuilt shared FFmpeg from the deps install. Windows has no pkg-config,
|
# Prebuilt shared FFmpeg from the deps install. Windows has no pkg-config,
|
||||||
@@ -929,6 +933,7 @@ elseif (WIN32)
|
|||||||
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include)
|
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include)
|
||||||
else ()
|
else ()
|
||||||
pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET
|
pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET
|
||||||
|
libavformat
|
||||||
libavcodec
|
libavcodec
|
||||||
libswscale
|
libswscale
|
||||||
libavutil
|
libavutil
|
||||||
|
|||||||
@@ -45,6 +45,25 @@ int AVVideoDecoder::open(Bambu_StreamInfo const &info)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int AVVideoDecoder::open(AVCodecParameters const ¶meters)
|
||||||
|
{
|
||||||
|
if (avcodec_parameters_to_context(codec_ctx_, ¶meters) < 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 AVVideoDecoder::decode(const Bambu_Sample &sample)
|
||||||
{
|
{
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
@@ -71,6 +90,14 @@ int AVVideoDecoder::decode(const Bambu_Sample &sample)
|
|||||||
return ret;
|
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 AVVideoDecoder::flush()
|
||||||
{
|
{
|
||||||
int ret = avcodec_send_packet(codec_ctx_, nullptr);
|
int ret = avcodec_send_packet(codec_ctx_, nullptr);
|
||||||
|
|||||||
@@ -23,8 +23,10 @@ public:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
int open(Bambu_StreamInfo const &info);
|
int open(Bambu_StreamInfo const &info);
|
||||||
|
int open(AVCodecParameters const ¶meters);
|
||||||
|
|
||||||
int decode(Bambu_Sample const &sample);
|
int decode(Bambu_Sample const &sample);
|
||||||
|
int decode(AVPacket const &packet);
|
||||||
|
|
||||||
int flush();
|
int flush();
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ wxEND_EVENT_TABLE()
|
|||||||
|
|
||||||
wxDEFINE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
|
wxDEFINE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
|
||||||
wxDEFINE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
|
wxDEFINE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
|
||||||
wxDEFINE_EVENT(EVT_CAM_SOURCE_CHANGE, wxCommandEvent);
|
|
||||||
|
|
||||||
#define CAMERAPOPUP_CLICK_INTERVAL 20
|
#define CAMERAPOPUP_CLICK_INTERVAL 20
|
||||||
|
|
||||||
@@ -102,34 +101,6 @@ CameraPopup::CameraPopup(wxWindow *parent)
|
|||||||
top_sizer->Add(0, 0, wxALL, 0);
|
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));
|
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)
|
auto url = wxString(L"https://www.orcaslicer.com/wiki/"); // Orca: neutral wiki link (vendor URL removed)
|
||||||
@@ -184,37 +155,6 @@ void CameraPopup::sdcard_absent_hint()
|
|||||||
GetEventHandler()->ProcessEvent(evt);
|
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)
|
void CameraPopup::on_switch_recording(wxCommandEvent& event)
|
||||||
{
|
{
|
||||||
if (!m_obj) return;
|
if (!m_obj) return;
|
||||||
|
|||||||
@@ -15,14 +15,12 @@
|
|||||||
#include "Widgets/SwitchButton.hpp"
|
#include "Widgets/SwitchButton.hpp"
|
||||||
#include "Widgets/RadioBox.hpp"
|
#include "Widgets/RadioBox.hpp"
|
||||||
#include "Widgets/PopupWindow.hpp"
|
#include "Widgets/PopupWindow.hpp"
|
||||||
#include "Widgets/TextInput.hpp"
|
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace GUI {
|
namespace GUI {
|
||||||
|
|
||||||
wxDECLARE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
|
wxDECLARE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
|
||||||
wxDECLARE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
|
wxDECLARE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
|
||||||
wxDECLARE_EVENT(EVT_CAM_SOURCE_CHANGE, wxCommandEvent);
|
|
||||||
|
|
||||||
class CameraPopup : public PopupWindow
|
class CameraPopup : public PopupWindow
|
||||||
{
|
{
|
||||||
@@ -53,9 +51,6 @@ protected:
|
|||||||
void on_switch_recording(wxCommandEvent& event);
|
void on_switch_recording(wxCommandEvent& event);
|
||||||
void on_set_resolution();
|
void on_set_resolution();
|
||||||
void sdcard_absent_hint();
|
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);
|
wxWindow * create_item_radiobox(wxString title, wxWindow *parent, wxString tooltip, int padding_left);
|
||||||
void select_curr_radiobox(int btn_idx);
|
void select_curr_radiobox(int btn_idx);
|
||||||
@@ -74,10 +69,6 @@ private:
|
|||||||
SwitchButton* m_switch_vcamera;
|
SwitchButton* m_switch_vcamera;
|
||||||
wxStaticText* m_text_liveview_retry;
|
wxStaticText* m_text_liveview_retry;
|
||||||
SwitchButton* m_switch_liveview_retry;
|
SwitchButton* m_switch_liveview_retry;
|
||||||
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;
|
wxStaticText* m_text_resolution;
|
||||||
wxWindow* m_resolution_options[RESOLUTION_OPTIONS_NUM];
|
wxWindow* m_resolution_options[RESOLUTION_OPTIONS_NUM];
|
||||||
wxScrolledWindow *m_panel;
|
wxScrolledWindow *m_panel;
|
||||||
|
|||||||
@@ -1338,7 +1338,6 @@ int MachineObject::command_get_access_code() {
|
|||||||
return this->publish_json(j);
|
return this->publish_json(j);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int MachineObject::command_request_push_all(bool request_now)
|
int MachineObject::command_request_push_all(bool request_now)
|
||||||
{
|
{
|
||||||
auto curr_time = std::chrono::system_clock::now();
|
auto curr_time = std::chrono::system_clock::now();
|
||||||
|
|||||||
@@ -749,6 +749,7 @@ public:
|
|||||||
int command_set_printer_nozzle(std::string nozzle_type, float diameter);
|
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_set_printer_nozzle2(int id, std::string nozzle_type, float diameter);
|
||||||
int command_get_access_code();
|
int command_get_access_code();
|
||||||
|
int command_start_camera();
|
||||||
int command_ack_proceed(json& proceed);
|
int command_ack_proceed(json& proceed);
|
||||||
int command_purification_disable();
|
int command_purification_disable();
|
||||||
int command_dont_remind_next_time(json& mqtt_guard_json);
|
int command_dont_remind_next_time(json& mqtt_guard_json);
|
||||||
@@ -797,7 +798,6 @@ public:
|
|||||||
int command_ams_select_tray(std::string tray_id);
|
int command_ams_select_tray(std::string tray_id);
|
||||||
int command_ams_refresh_rfid(std::string tray_id);
|
int command_ams_refresh_rfid(std::string tray_id);
|
||||||
int command_ams_refresh_rfid2(int ams_id, int slot_id);
|
int command_ams_refresh_rfid2(int ams_id, int slot_id);
|
||||||
int command_start_camera();
|
|
||||||
int command_ams_control(std::string action);
|
int command_ams_control(std::string action);
|
||||||
int command_ams_drying_stop();
|
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 = "");
|
int command_start_extrusion_cali(int tray_index, int nozzle_temp, int bed_temp, float max_volumetric_speed, std::string setting_id = "");
|
||||||
|
|||||||
35
src/slic3r/GUI/IMediaController.hpp
Normal file
35
src/slic3r/GUI/IMediaController.hpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <wx/mediactrl.h>
|
||||||
|
#include <wx/uri.h>
|
||||||
|
|
||||||
|
#include <slic3r/Utils/IPrinterAgent.hpp>
|
||||||
|
|
||||||
|
namespace Slic3r { namespace GUI {
|
||||||
|
|
||||||
|
class IMediaController
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void Load(wxURI url) = 0;
|
||||||
|
|
||||||
|
// The default keeps existing media controllers unaware of camera-specific modes.
|
||||||
|
virtual void Load(wxURI url, CameraStreamMode mode)
|
||||||
|
{
|
||||||
|
(void) mode;
|
||||||
|
Load(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Play() = 0;
|
||||||
|
|
||||||
|
virtual void Stop() = 0;
|
||||||
|
|
||||||
|
virtual wxMediaState GetState() { return wxMediaState{}; }
|
||||||
|
|
||||||
|
virtual int GetLastError() const { return {}; };
|
||||||
|
|
||||||
|
virtual wxSize GetVideoSize() const { return {}; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
}} // namespace Slic3r::GUI
|
||||||
@@ -146,8 +146,48 @@ MediaPlayCtrl::~MediaPlayCtrl()
|
|||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << this;
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MediaPlayCtrl::SetWebMediaController(IMediaController *ctrl)
|
||||||
|
{
|
||||||
|
m_web_ctrl = ctrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
CameraStreamMode MediaPlayCtrl::current_mode() const
|
||||||
|
{
|
||||||
|
auto agent = wxGetApp().getAgent();
|
||||||
|
return agent ? agent->get_camera_stream_mode() : CameraStreamMode::none;
|
||||||
|
}
|
||||||
|
|
||||||
void MediaPlayCtrl::SetMachineObject(MachineObject* obj)
|
void MediaPlayCtrl::SetMachineObject(MachineObject* obj)
|
||||||
{
|
{
|
||||||
|
switch (current_mode()) {
|
||||||
|
case CameraStreamMode::http:
|
||||||
|
case CameraStreamMode::http_snapshot:
|
||||||
|
case CameraStreamMode::rtsp: {
|
||||||
|
std::string machine = obj ? obj->get_dev_id() : "";
|
||||||
|
auto agent = wxGetApp().getAgent();
|
||||||
|
std::string url = agent ? agent->get_local_camera_stream_url() : "";
|
||||||
|
m_camera_exists = !url.empty();
|
||||||
|
Enable(obj && m_camera_exists);
|
||||||
|
bool changed = machine != m_machine || url != m_agent_camera_url;
|
||||||
|
m_machine = machine;
|
||||||
|
m_agent_camera_url = url;
|
||||||
|
m_url = from_u8(url);
|
||||||
|
if (!changed) {
|
||||||
|
if (m_last_state == MEDIASTATE_IDLE && IsEnabled() && !m_web_user_stopped)
|
||||||
|
Play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_web_user_stopped = false;
|
||||||
|
if (m_last_state != MEDIASTATE_IDLE)
|
||||||
|
Stop(" ");
|
||||||
|
if (IsEnabled())
|
||||||
|
Play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
std::string machine = obj ? obj->get_dev_id() : "";
|
std::string machine = obj ? obj->get_dev_id() : "";
|
||||||
if (obj) {
|
if (obj) {
|
||||||
m_camera_exists = obj->has_ipcam;
|
m_camera_exists = obj->has_ipcam;
|
||||||
@@ -249,6 +289,42 @@ void refresh_agora_url(char const* device, char const* dev_ver, char const* chan
|
|||||||
|
|
||||||
void MediaPlayCtrl::Play()
|
void MediaPlayCtrl::Play()
|
||||||
{
|
{
|
||||||
|
switch (current_mode()) {
|
||||||
|
case CameraStreamMode::http:
|
||||||
|
case CameraStreamMode::http_snapshot:
|
||||||
|
if (!m_next_retry.IsValid() || wxDateTime::Now() < m_next_retry)
|
||||||
|
return;
|
||||||
|
if (!IsShownOnScreen()) return;
|
||||||
|
if (m_last_state != MEDIASTATE_IDLE) return;
|
||||||
|
if (m_machine.empty() || !IsEnabled() || !m_camera_exists || m_url.IsEmpty() || !m_web_ctrl) {
|
||||||
|
Stop(_L("Please confirm if the printer is connected."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (auto agent = wxGetApp().getAgent())
|
||||||
|
agent->command_start_camera(m_machine);
|
||||||
|
m_button_play->SetIcon("media_stop");
|
||||||
|
m_web_ctrl->Load(wxURI(m_url), current_mode());
|
||||||
|
m_web_ctrl->Play();
|
||||||
|
m_last_state = wxMEDIASTATE_PLAYING;
|
||||||
|
SetStatus(_L("Playing..."), false);
|
||||||
|
return;
|
||||||
|
case CameraStreamMode::rtsp:
|
||||||
|
if (m_next_retry.IsValid() && wxDateTime::Now() < m_next_retry)
|
||||||
|
return;
|
||||||
|
if (!IsShownOnScreen()) return;
|
||||||
|
if (m_last_state != MEDIASTATE_IDLE) return;
|
||||||
|
m_failed_code = 0;
|
||||||
|
if (m_machine.empty() || !IsEnabled() || !m_camera_exists || m_url.IsEmpty()) {
|
||||||
|
Stop(_L("Please confirm if the printer is connected."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_button_play->SetIcon("media_stop");
|
||||||
|
load();
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (!m_next_retry.IsValid() || wxDateTime::Now() < m_next_retry)
|
if (!m_next_retry.IsValid() || wxDateTime::Now() < m_next_retry)
|
||||||
return;
|
return;
|
||||||
if (!IsShownOnScreen())
|
if (!IsShownOnScreen())
|
||||||
@@ -376,8 +452,38 @@ void MediaPlayCtrl::Play()
|
|||||||
|
|
||||||
void start_ping_test();
|
void start_ping_test();
|
||||||
|
|
||||||
|
void MediaPlayCtrl::StopWebStream()
|
||||||
|
{
|
||||||
|
if (m_last_state == MEDIASTATE_IDLE)
|
||||||
|
return;
|
||||||
|
if (m_web_ctrl)
|
||||||
|
m_web_ctrl->Stop();
|
||||||
|
m_button_play->SetIcon("media_play");
|
||||||
|
m_last_state = MEDIASTATE_IDLE;
|
||||||
|
SetStatus(_L("Video Stopped."), false);
|
||||||
|
}
|
||||||
|
|
||||||
void MediaPlayCtrl::Stop(wxString const &msg, wxString const &msg2)
|
void MediaPlayCtrl::Stop(wxString const &msg, wxString const &msg2)
|
||||||
{
|
{
|
||||||
|
switch (current_mode()) {
|
||||||
|
case CameraStreamMode::http:
|
||||||
|
case CameraStreamMode::http_snapshot:
|
||||||
|
if (m_last_state != MEDIASTATE_IDLE) {
|
||||||
|
if (m_web_ctrl) m_web_ctrl->Stop();
|
||||||
|
m_button_play->SetIcon("media_play");
|
||||||
|
m_last_state = MEDIASTATE_IDLE;
|
||||||
|
if (!msg.IsEmpty())
|
||||||
|
SetStatus(msg);
|
||||||
|
else
|
||||||
|
SetStatus(_L("Video Stopped."), false);
|
||||||
|
} else if (!msg.IsEmpty()) {
|
||||||
|
SetStatus(msg, false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
int last_state = m_last_state;
|
int last_state = m_last_state;
|
||||||
|
|
||||||
if (m_last_state != MEDIASTATE_IDLE) {
|
if (m_last_state != MEDIASTATE_IDLE) {
|
||||||
@@ -454,10 +560,12 @@ void MediaPlayCtrl::TogglePlay()
|
|||||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::TogglePlay";
|
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::TogglePlay";
|
||||||
if (m_last_state != MEDIASTATE_IDLE) {
|
if (m_last_state != MEDIASTATE_IDLE) {
|
||||||
m_next_retry = wxDateTime();
|
m_next_retry = wxDateTime();
|
||||||
|
m_web_user_stopped = true;
|
||||||
Stop();
|
Stop();
|
||||||
} else {
|
} else {
|
||||||
m_failed_retry = 0;
|
m_failed_retry = 0;
|
||||||
m_user_triggered = true;
|
m_user_triggered = true;
|
||||||
|
m_web_user_stopped = false;
|
||||||
if (m_last_user_play + wxTimeSpan::Minutes(5) < wxDateTime::Now()) {
|
if (m_last_user_play + wxTimeSpan::Minutes(5) < wxDateTime::Now()) {
|
||||||
m_last_failed_codes.clear();
|
m_last_failed_codes.clear();
|
||||||
m_last_user_play = wxDateTime::Now();
|
m_last_user_play = wxDateTime::Now();
|
||||||
@@ -661,7 +769,7 @@ void MediaPlayCtrl::load()
|
|||||||
{
|
{
|
||||||
m_last_state = MEDIASTATE_LOADING;
|
m_last_state = MEDIASTATE_LOADING;
|
||||||
SetStatus(_L("Loading..."));
|
SetStatus(_L("Loading..."));
|
||||||
if (wxGetApp().app_config->get("internal_developer_mode") == "true") {
|
if (current_mode() != CameraStreamMode::rtsp) {
|
||||||
std::string file_h264 = data_dir() + "/video.h264";
|
std::string file_h264 = data_dir() + "/video.h264";
|
||||||
std::string file_info = data_dir() + "/video.info";
|
std::string file_info = data_dir() + "/video.info";
|
||||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl dump video to " << file_h264;
|
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl dump video to " << file_h264;
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#define MediaPlayCtrl_h
|
#define MediaPlayCtrl_h
|
||||||
|
|
||||||
#include "wxMediaCtrl3.h"
|
#include "wxMediaCtrl3.h"
|
||||||
|
#include "IMediaController.hpp"
|
||||||
|
#include "slic3r/Utils/IPrinterAgent.hpp"
|
||||||
|
|
||||||
#include <wx/panel.h>
|
#include <wx/panel.h>
|
||||||
|
|
||||||
@@ -36,6 +38,10 @@ public:
|
|||||||
|
|
||||||
void SetMachineObject(MachineObject * obj);
|
void SetMachineObject(MachineObject * obj);
|
||||||
|
|
||||||
|
void SetWebMediaController(IMediaController *ctrl);
|
||||||
|
|
||||||
|
void StopWebStream();
|
||||||
|
|
||||||
bool IsStreaming() const;
|
bool IsStreaming() const;
|
||||||
|
|
||||||
void ToggleStream();
|
void ToggleStream();
|
||||||
@@ -66,6 +72,8 @@ private:
|
|||||||
|
|
||||||
static bool get_stream_url(std::string *url = nullptr);
|
static bool get_stream_url(std::string *url = nullptr);
|
||||||
|
|
||||||
|
CameraStreamMode current_mode() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static inline const wxMediaState MEDIASTATE_IDLE = static_cast<wxMediaState>(3);
|
static inline const wxMediaState MEDIASTATE_IDLE = static_cast<wxMediaState>(3);
|
||||||
static inline const wxMediaState MEDIASTATE_INITIALIZING = static_cast<wxMediaState>(4);
|
static inline const wxMediaState MEDIASTATE_INITIALIZING = static_cast<wxMediaState>(4);
|
||||||
@@ -76,6 +84,9 @@ private:
|
|||||||
std::shared_ptr<int> m_token = std::make_shared<int>(0);
|
std::shared_ptr<int> m_token = std::make_shared<int>(0);
|
||||||
|
|
||||||
wxMediaCtrl3 * m_media_ctrl;
|
wxMediaCtrl3 * m_media_ctrl;
|
||||||
|
IMediaController * m_web_ctrl = nullptr;
|
||||||
|
std::string m_agent_camera_url;
|
||||||
|
bool m_web_user_stopped = false;
|
||||||
wxMediaState m_last_state = MEDIASTATE_IDLE;
|
wxMediaState m_last_state = MEDIASTATE_IDLE;
|
||||||
std::string m_machine;
|
std::string m_machine;
|
||||||
int m_lan_proto = 0;
|
int m_lan_proto = 0;
|
||||||
|
|||||||
@@ -1494,27 +1494,26 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page()
|
|||||||
m_setting_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24)));
|
m_setting_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24)));
|
||||||
m_setting_button->SetBackgroundColour(STATUS_TITLE_BG);
|
m_setting_button->SetBackgroundColour(STATUS_TITLE_BG);
|
||||||
|
|
||||||
m_camera_switch_button = new wxStaticBitmap(m_panel_monitoring_title, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize(FromDIP(38), FromDIP(24)), 0);
|
// m_camera_switch_button = new wxStaticBitmap(m_panel_monitoring_title, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize(FromDIP(38), FromDIP(24)), 0);
|
||||||
m_camera_switch_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24)));
|
// m_camera_switch_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24)));
|
||||||
m_camera_switch_button->SetBackgroundColour(STATUS_TITLE_BG);
|
// m_camera_switch_button->SetBackgroundColour(STATUS_TITLE_BG);
|
||||||
m_camera_switch_button->SetBitmap(m_bitmap_switch_camera.bmp());
|
// m_camera_switch_button->SetBitmap(m_bitmap_switch_camera.bmp());
|
||||||
m_camera_switch_button->Bind(wxEVT_LEFT_DOWN, &StatusBasePanel::on_camera_switch_toggled, this);
|
// m_camera_switch_button->Bind(wxEVT_RIGHT_DOWN, [this](auto& e) {
|
||||||
m_camera_switch_button->Bind(wxEVT_RIGHT_DOWN, [this](auto& e) {
|
// const std::string js_request_pip = R"(
|
||||||
const std::string js_request_pip = R"(
|
// document.querySelector('video').requestPictureInPicture();
|
||||||
document.querySelector('video').requestPictureInPicture();
|
// )";
|
||||||
)";
|
// m_custom_camera_view->RunScript(js_request_pip);
|
||||||
m_custom_camera_view->RunScript(js_request_pip);
|
// });
|
||||||
});
|
// m_camera_switch_button->Hide();
|
||||||
m_camera_switch_button->Hide();
|
|
||||||
|
|
||||||
m_bitmap_sdcard_img->SetToolTip(_L("Storage"));
|
m_bitmap_sdcard_img->SetToolTip(_L("Storage"));
|
||||||
m_bitmap_timelapse_img->SetToolTip(_L("Timelapse"));
|
m_bitmap_timelapse_img->SetToolTip(_L("Timelapse"));
|
||||||
m_bitmap_recording_img->SetToolTip(_L("Video"));
|
m_bitmap_recording_img->SetToolTip(_L("Video"));
|
||||||
m_bitmap_vcamera_img->SetToolTip(_L("Go Live"));
|
m_bitmap_vcamera_img->SetToolTip(_L("Go Live"));
|
||||||
m_setting_button->SetToolTip(_L("Camera Setting"));
|
m_setting_button->SetToolTip(_L("Camera Setting"));
|
||||||
m_camera_switch_button->SetToolTip(_L("Switch Camera View"));
|
// m_camera_switch_button->SetToolTip(_L("Switch Camera View"));
|
||||||
|
|
||||||
bSizer_monitoring_title->Add(m_camera_switch_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
// bSizer_monitoring_title->Add(m_camera_switch_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
||||||
bSizer_monitoring_title->Add(m_bitmap_sdcard_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
bSizer_monitoring_title->Add(m_bitmap_sdcard_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
||||||
bSizer_monitoring_title->Add(m_bitmap_timelapse_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
bSizer_monitoring_title->Add(m_bitmap_timelapse_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
||||||
bSizer_monitoring_title->Add(m_bitmap_recording_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
bSizer_monitoring_title->Add(m_bitmap_recording_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
||||||
@@ -1537,19 +1536,18 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page()
|
|||||||
m_custom_camera_view = WebView::CreateWebView(this, wxEmptyString);
|
m_custom_camera_view = WebView::CreateWebView(this, wxEmptyString);
|
||||||
m_custom_camera_view->EnableContextMenu(false);
|
m_custom_camera_view->EnableContextMenu(false);
|
||||||
Bind(wxEVT_WEBVIEW_NAVIGATING, &StatusBasePanel::on_webview_navigating, this, m_custom_camera_view->GetId());
|
Bind(wxEVT_WEBVIEW_NAVIGATING, &StatusBasePanel::on_webview_navigating, this, m_custom_camera_view->GetId());
|
||||||
|
m_web_media_controller = std::make_unique<WebMediaController>(m_custom_camera_view);
|
||||||
|
|
||||||
m_media_play_ctrl = new MediaPlayCtrl(this, m_media_ctrl, wxDefaultPosition, wxSize(-1, FromDIP(40)));
|
m_media_play_ctrl = new MediaPlayCtrl(this, m_media_ctrl, wxDefaultPosition, wxSize(-1, FromDIP(40)));
|
||||||
|
m_media_play_ctrl->SetWebMediaController(m_web_media_controller.get());
|
||||||
m_custom_camera_view->Hide();
|
m_custom_camera_view->Hide();
|
||||||
m_custom_camera_view->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, [this](wxWebViewEvent& evt) {
|
// m_custom_camera_view->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, [this](wxWebViewEvent& evt) {
|
||||||
if (evt.GetString() == "leavepictureinpicture") {
|
// if (evt.GetString() == "leavepictureinpicture") {
|
||||||
// When leaving PiP, video gets paused in some cases and toggling play
|
// // When leaving PiP, video gets paused in some cases and toggling play
|
||||||
// programmatically does not work.
|
// // programmatically does not work.
|
||||||
m_custom_camera_view->Reload();
|
// m_custom_camera_view->Reload();
|
||||||
}
|
// }
|
||||||
else if (evt.GetString() == "enterpictureinpicture") {
|
// });
|
||||||
toggle_builtin_camera();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
sizer->Add(m_media_ctrl, 1, wxEXPAND | wxALL, 0);
|
sizer->Add(m_media_ctrl, 1, wxEXPAND | wxALL, 0);
|
||||||
sizer->Add(m_custom_camera_view, 1, wxEXPAND | wxALL, 0);
|
sizer->Add(m_custom_camera_view, 1, wxEXPAND | wxALL, 0);
|
||||||
@@ -1559,10 +1557,6 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page()
|
|||||||
//
|
//
|
||||||
// sizer->Add(media_ctrl_panel, 1, wxEXPAND | wxALL, 1);
|
// sizer->Add(media_ctrl_panel, 1, wxEXPAND | wxALL, 1);
|
||||||
|
|
||||||
if (wxGetApp().app_config->get("camera", "enable_custom_source") == "true") {
|
|
||||||
handle_camera_source_change();
|
|
||||||
}
|
|
||||||
|
|
||||||
return sizer;
|
return sizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2312,25 +2306,21 @@ void StatusPanel::update_camera_state(MachineObject* obj)
|
|||||||
{
|
{
|
||||||
if (!obj) return;
|
if (!obj) return;
|
||||||
|
|
||||||
const bool has_printer_webcam = !obj->webcam_stream_url.empty();
|
auto agent = wxGetApp().getAgent();
|
||||||
|
const auto camera_mode = agent ? agent->get_camera_stream_mode() : CameraStreamMode::none;
|
||||||
|
const bool has_printer_webcam = camera_mode == CameraStreamMode::http || camera_mode == CameraStreamMode::http_snapshot;
|
||||||
if (has_printer_webcam) {
|
if (has_printer_webcam) {
|
||||||
if (m_printer_webcam_url != obj->webcam_stream_url) {
|
//m_camera_switch_button->Hide();
|
||||||
m_custom_camera_view->LoadURL(obj->webcam_stream_url);
|
|
||||||
m_custom_camera_view->Show();
|
|
||||||
m_media_ctrl->Hide();
|
|
||||||
m_media_play_ctrl->Hide();
|
|
||||||
m_printer_webcam_url = obj->webcam_stream_url;
|
|
||||||
}
|
|
||||||
m_camera_switch_button->Hide();
|
|
||||||
if (!m_custom_camera_view->IsShown()) {
|
if (!m_custom_camera_view->IsShown()) {
|
||||||
// why: do not compare or reload the WebView URL per tick, or redirects can cause a reload loop.
|
// why: do not reload the WebView URL per tick, or redirects can cause a reload loop.
|
||||||
|
// MediaPlayCtrl (via its WebMediaController) owns loading/playing the stream itself.
|
||||||
m_custom_camera_view->Show();
|
m_custom_camera_view->Show();
|
||||||
m_media_ctrl->Hide();
|
m_media_ctrl->Hide();
|
||||||
m_media_play_ctrl->Hide();
|
|
||||||
}
|
}
|
||||||
} else if (!m_printer_webcam_url.empty()) {
|
} else if (m_custom_camera_view->IsShown()) {
|
||||||
handle_camera_source_change();
|
m_custom_camera_view->Hide();
|
||||||
m_printer_webcam_url.clear();
|
m_media_ctrl->Show();
|
||||||
|
m_media_play_ctrl->StopWebStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
//sdcard
|
//sdcard
|
||||||
@@ -5056,7 +5046,6 @@ void StatusPanel::on_camera_enter(wxMouseEvent& event)
|
|||||||
}
|
}
|
||||||
sdcard_hint_dlg->on_show();
|
sdcard_hint_dlg->on_show();
|
||||||
});
|
});
|
||||||
m_camera_popup->Bind(EVT_CAM_SOURCE_CHANGE, &StatusPanel::on_camera_source_change, this);
|
|
||||||
wxWindow* ctrl = (wxWindow*)event.GetEventObject();
|
wxWindow* ctrl = (wxWindow*)event.GetEventObject();
|
||||||
wxPoint pos = ctrl->ClientToScreen(wxPoint(0, 0));
|
wxPoint pos = ctrl->ClientToScreen(wxPoint(0, 0));
|
||||||
wxSize sz = ctrl->GetSize();
|
wxSize sz = ctrl->GetSize();
|
||||||
@@ -5068,54 +5057,6 @@ void StatusPanel::on_camera_enter(wxMouseEvent& event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StatusBasePanel::on_camera_source_change(wxCommandEvent& event)
|
|
||||||
{
|
|
||||||
handle_camera_source_change();
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusBasePanel::handle_camera_source_change()
|
|
||||||
{
|
|
||||||
const auto new_cam_url = wxGetApp().app_config->get("camera", "custom_source");
|
|
||||||
const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true";
|
|
||||||
|
|
||||||
if (enabled && !new_cam_url.empty()) {
|
|
||||||
m_custom_camera_view->LoadURL(new_cam_url);
|
|
||||||
toggle_custom_camera();
|
|
||||||
m_camera_switch_button->Show();
|
|
||||||
} else {
|
|
||||||
toggle_builtin_camera();
|
|
||||||
m_camera_switch_button->Hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusBasePanel::toggle_builtin_camera()
|
|
||||||
{
|
|
||||||
m_custom_camera_view->Hide();
|
|
||||||
m_media_ctrl->Show();
|
|
||||||
m_media_play_ctrl->Show();
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusBasePanel::toggle_custom_camera()
|
|
||||||
{
|
|
||||||
const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true";
|
|
||||||
|
|
||||||
if (enabled) {
|
|
||||||
m_custom_camera_view->Show();
|
|
||||||
m_media_ctrl->Hide();
|
|
||||||
m_media_play_ctrl->Hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusBasePanel::on_camera_switch_toggled(wxMouseEvent& event)
|
|
||||||
{
|
|
||||||
const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true";
|
|
||||||
if (enabled && m_media_ctrl->IsShown()) {
|
|
||||||
toggle_custom_camera();
|
|
||||||
} else {
|
|
||||||
toggle_builtin_camera();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusBasePanel::remove_controls()
|
void StatusBasePanel::remove_controls()
|
||||||
{
|
{
|
||||||
const std::string js_cleanup_video_element = R"(
|
const std::string js_cleanup_video_element = R"(
|
||||||
@@ -5268,9 +5209,10 @@ bool StatusPanel::is_stage_list_info_changed(MachineObject *obj)
|
|||||||
void StatusPanel::set_default()
|
void StatusPanel::set_default()
|
||||||
{
|
{
|
||||||
BOOST_LOG_TRIVIAL(trace) << "status_panel: set_default";
|
BOOST_LOG_TRIVIAL(trace) << "status_panel: set_default";
|
||||||
if (!m_printer_webcam_url.empty()) {
|
if (m_custom_camera_view->IsShown()) {
|
||||||
handle_camera_source_change();
|
m_custom_camera_view->Hide();
|
||||||
m_printer_webcam_url.clear();
|
m_media_ctrl->Show();
|
||||||
|
m_media_play_ctrl->StopWebStream();
|
||||||
}
|
}
|
||||||
obj = nullptr;
|
obj = nullptr;
|
||||||
last_subtask = nullptr;
|
last_subtask = nullptr;
|
||||||
|
|||||||
@@ -15,7 +15,9 @@
|
|||||||
#include <wx/gbsizer.h>
|
#include <wx/gbsizer.h>
|
||||||
#include <wx/webrequest.h>
|
#include <wx/webrequest.h>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <memory>
|
||||||
#include "MediaPlayCtrl.h"
|
#include "MediaPlayCtrl.h"
|
||||||
|
#include "WebMediaController.hpp"
|
||||||
#include "AMSSetting.hpp"
|
#include "AMSSetting.hpp"
|
||||||
#include "Calibration.hpp"
|
#include "Calibration.hpp"
|
||||||
#include "CalibrationWizardPage.hpp"
|
#include "CalibrationWizardPage.hpp"
|
||||||
@@ -440,7 +442,7 @@ protected:
|
|||||||
wxStaticBitmap *m_bitmap_sdcard_img;
|
wxStaticBitmap *m_bitmap_sdcard_img;
|
||||||
wxStaticBitmap *m_bitmap_static_use_time;
|
wxStaticBitmap *m_bitmap_static_use_time;
|
||||||
wxStaticBitmap *m_bitmap_static_use_weight;
|
wxStaticBitmap *m_bitmap_static_use_weight;
|
||||||
wxStaticBitmap* m_camera_switch_button;
|
// wxStaticBitmap* m_camera_switch_button;
|
||||||
|
|
||||||
|
|
||||||
wxMediaCtrl3 * m_media_ctrl;
|
wxMediaCtrl3 * m_media_ctrl;
|
||||||
@@ -462,6 +464,8 @@ protected:
|
|||||||
ScalableButton *m_button_abort;
|
ScalableButton *m_button_abort;
|
||||||
Button * m_button_clean;
|
Button * m_button_clean;
|
||||||
wxWebView * m_custom_camera_view{nullptr};
|
wxWebView * m_custom_camera_view{nullptr};
|
||||||
|
std::unique_ptr<WebMediaController> m_web_media_controller;
|
||||||
|
|
||||||
wxSimplebook* m_extruder_book;
|
wxSimplebook* m_extruder_book;
|
||||||
std::vector<ExtruderImage *> m_extruderImage;
|
std::vector<ExtruderImage *> m_extruderImage;
|
||||||
|
|
||||||
@@ -577,13 +581,8 @@ protected:
|
|||||||
virtual void on_axis_ctrl_e_up_10(wxCommandEvent &event) { event.Skip(); }
|
virtual void on_axis_ctrl_e_up_10(wxCommandEvent &event) { event.Skip(); }
|
||||||
virtual void on_axis_ctrl_e_down_10(wxCommandEvent &event) { event.Skip(); }
|
virtual void on_axis_ctrl_e_down_10(wxCommandEvent &event) { event.Skip(); }
|
||||||
virtual void on_nozzle_selected(wxCommandEvent &event) { event.Skip(); }
|
virtual void on_nozzle_selected(wxCommandEvent &event) { event.Skip(); }
|
||||||
void on_camera_source_change(wxCommandEvent& event);
|
|
||||||
void handle_camera_source_change();
|
|
||||||
void remove_controls();
|
void remove_controls();
|
||||||
void on_webview_navigating(wxWebViewEvent& evt);
|
void on_webview_navigating(wxWebViewEvent& evt);
|
||||||
void on_camera_switch_toggled(wxMouseEvent& event);
|
|
||||||
void toggle_custom_camera();
|
|
||||||
void toggle_builtin_camera();
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
StatusBasePanel(wxWindow * parent,
|
StatusBasePanel(wxWindow * parent,
|
||||||
@@ -664,7 +663,6 @@ protected:
|
|||||||
int m_last_timelapse = -1;
|
int m_last_timelapse = -1;
|
||||||
int m_last_extrusion = -1;
|
int m_last_extrusion = -1;
|
||||||
int m_last_vcamera = -1;
|
int m_last_vcamera = -1;
|
||||||
std::string m_printer_webcam_url;
|
|
||||||
int m_model_mall_request_count = 0;
|
int m_model_mall_request_count = 0;
|
||||||
bool m_is_load_with_temp = false;
|
bool m_is_load_with_temp = false;
|
||||||
json m_rating_result;
|
json m_rating_result;
|
||||||
|
|||||||
83
src/slic3r/GUI/WebMediaController.cpp
Normal file
83
src/slic3r/GUI/WebMediaController.cpp
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
#include "WebMediaController.hpp"
|
||||||
|
|
||||||
|
#include <wx/webview.h>
|
||||||
|
|
||||||
|
namespace Slic3r { namespace GUI {
|
||||||
|
|
||||||
|
WebMediaController::WebMediaController(wxWebView *webview) : m_webview(webview)
|
||||||
|
{
|
||||||
|
if (!m_webview)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_webview->SetBackgroundColour(*wxBLACK);
|
||||||
|
m_webview->SetPage(
|
||||||
|
"<html><head><style>html,body{margin:0;height:100%;background:#000;}</style></head><body></body></html>",
|
||||||
|
"");
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebMediaController::Load(wxURI url)
|
||||||
|
{
|
||||||
|
Load(url, CameraStreamMode::http);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebMediaController::Load(wxURI url, CameraStreamMode mode)
|
||||||
|
{
|
||||||
|
m_url = url.BuildURI().ToStdString();
|
||||||
|
m_stream_mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebMediaController::Play()
|
||||||
|
{
|
||||||
|
if (!m_webview)
|
||||||
|
return;
|
||||||
|
|
||||||
|
wxString url = wxString::FromUTF8(m_url);
|
||||||
|
wxString html = "<html><head><style>"
|
||||||
|
"html,body{margin:0;height:100%;background:#000;overflow:hidden;}"
|
||||||
|
"img{width:100%;height:100%;object-fit:contain;display:block;}"
|
||||||
|
"</style></head><body><img id=\"camera-frame\"";
|
||||||
|
if (m_stream_mode == CameraStreamMode::http_snapshot) {
|
||||||
|
html += " data-camera-url=\"" + url + "\"><script>"
|
||||||
|
"const cameraFrame=document.getElementById('camera-frame');"
|
||||||
|
"const cameraUrl=cameraFrame.dataset.cameraUrl;"
|
||||||
|
"let cameraFrameLoading=false;"
|
||||||
|
"function refreshCameraFrame(){"
|
||||||
|
"if(cameraFrameLoading)return;"
|
||||||
|
"cameraFrameLoading=true;"
|
||||||
|
"const nextFrame=new Image();"
|
||||||
|
"nextFrame.onload=function(){cameraFrame.src=nextFrame.src;cameraFrameLoading=false;};"
|
||||||
|
"nextFrame.onerror=function(){cameraFrameLoading=false;};"
|
||||||
|
"nextFrame.src=cameraUrl+(cameraUrl.indexOf('?')>=0?'&':'?')+'_orca_frame='+Date.now();"
|
||||||
|
"}"
|
||||||
|
"refreshCameraFrame();"
|
||||||
|
"setInterval(refreshCameraFrame,200);"
|
||||||
|
"</script></body></html>";
|
||||||
|
} else {
|
||||||
|
html += " src=\"" + url + "\"></body></html>";
|
||||||
|
}
|
||||||
|
m_webview->SetPage(html, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebMediaController::Stop()
|
||||||
|
{
|
||||||
|
m_url.clear();
|
||||||
|
if (m_webview)
|
||||||
|
m_webview->Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// wxMediaState WebMediaController::GetState()
|
||||||
|
// {
|
||||||
|
// return wxMediaState{};
|
||||||
|
// }
|
||||||
|
|
||||||
|
// int WebMediaController::GetLastError() const
|
||||||
|
// {
|
||||||
|
// return 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// wxSize WebMediaController::GetVideoSize() const
|
||||||
|
// {
|
||||||
|
// return wxSize{};
|
||||||
|
// }
|
||||||
|
|
||||||
|
}} // namespace Slic3r::GUI
|
||||||
36
src/slic3r/GUI/WebMediaController.hpp
Normal file
36
src/slic3r/GUI/WebMediaController.hpp
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <slic3r/GUI/IMediaController.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class wxWebView;
|
||||||
|
|
||||||
|
namespace Slic3r { namespace GUI {
|
||||||
|
|
||||||
|
class WebMediaController : public IMediaController
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit WebMediaController(wxWebView *webview);
|
||||||
|
|
||||||
|
void Load(wxURI url) override;
|
||||||
|
|
||||||
|
void Load(wxURI url, CameraStreamMode mode) override;
|
||||||
|
|
||||||
|
void Play() override;
|
||||||
|
|
||||||
|
void Stop() override;
|
||||||
|
|
||||||
|
// wxMediaState GetState() override;
|
||||||
|
|
||||||
|
// int GetLastError() const override;
|
||||||
|
|
||||||
|
// wxSize GetVideoSize() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
wxWebView * m_webview;
|
||||||
|
std::string m_url;
|
||||||
|
CameraStreamMode m_stream_mode = CameraStreamMode::http;
|
||||||
|
};
|
||||||
|
|
||||||
|
}} // namespace Slic3r::GUI
|
||||||
@@ -4,6 +4,9 @@
|
|||||||
#include "libslic3r/Utils.hpp"
|
#include "libslic3r/Utils.hpp"
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
#include <wx/dcclient.h>
|
#include <wx/dcclient.h>
|
||||||
|
extern "C" {
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
}
|
||||||
#ifdef __WIN32__
|
#ifdef __WIN32__
|
||||||
#include <versionhelpers.h>
|
#include <versionhelpers.h>
|
||||||
#include <wx/msw/registry.h>
|
#include <wx/msw/registry.h>
|
||||||
@@ -185,6 +188,106 @@ void wxMediaCtrl3::bambu_log(void *ctx, int level, tchar const *msg2)
|
|||||||
BOOST_LOG_TRIVIAL(info) << msg.ToUTF8().data();
|
BOOST_LOG_TRIVIAL(info) << msg.ToUTF8().data();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int wxMediaCtrl3::rtsp_interrupt_callback(void *opaque)
|
||||||
|
{
|
||||||
|
auto *ctrl = static_cast<wxMediaCtrl3 *>(opaque);
|
||||||
|
std::lock_guard<std::mutex> lock(ctrl->m_mutex);
|
||||||
|
return ctrl->m_url != ctrl->m_active_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
int wxMediaCtrl3::PlayRtsp(std::shared_ptr<wxURI> const &url, std::unique_lock<std::mutex> &lock)
|
||||||
|
{
|
||||||
|
if (avformat_network_init() < 0)
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
AVFormatContext *format_context = avformat_alloc_context();
|
||||||
|
if (!format_context) {
|
||||||
|
avformat_network_deinit();
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
format_context->interrupt_callback = {&wxMediaCtrl3::rtsp_interrupt_callback, this};
|
||||||
|
m_active_url = url;
|
||||||
|
|
||||||
|
auto finish = [&](int error) {
|
||||||
|
lock.unlock();
|
||||||
|
avformat_close_input(&format_context);
|
||||||
|
avformat_network_deinit();
|
||||||
|
lock.lock();
|
||||||
|
m_active_url.reset();
|
||||||
|
return error;
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::string uri = url->BuildURI().ToUTF8().data();
|
||||||
|
AVDictionary *options = nullptr;
|
||||||
|
av_dict_set(&options, "rtsp_transport", "tcp", 0);
|
||||||
|
lock.unlock();
|
||||||
|
int error = avformat_open_input(&format_context, uri.c_str(), nullptr, &options);
|
||||||
|
av_dict_free(&options);
|
||||||
|
lock.lock();
|
||||||
|
if (error < 0)
|
||||||
|
return finish(2);
|
||||||
|
|
||||||
|
lock.unlock();
|
||||||
|
error = avformat_find_stream_info(format_context, nullptr);
|
||||||
|
lock.lock();
|
||||||
|
if (error < 0)
|
||||||
|
return finish(2);
|
||||||
|
|
||||||
|
const int video_stream = av_find_best_stream(format_context, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
|
||||||
|
if (video_stream < 0)
|
||||||
|
return finish(2);
|
||||||
|
|
||||||
|
AVVideoDecoder decoder;
|
||||||
|
if (decoder.open(*format_context->streams[video_stream]->codecpar) < 0)
|
||||||
|
return finish(2);
|
||||||
|
|
||||||
|
m_video_size = {format_context->streams[video_stream]->codecpar->width,
|
||||||
|
format_context->streams[video_stream]->codecpar->height};
|
||||||
|
if (!m_video_size.IsFullySpecified() || m_video_size.x <= 0 || m_video_size.y <= 0)
|
||||||
|
return finish(2);
|
||||||
|
adjust_frame_size(m_frame_size, m_video_size, GetSize());
|
||||||
|
NotifyStopped();
|
||||||
|
|
||||||
|
AVPacket *packet = av_packet_alloc();
|
||||||
|
if (!packet)
|
||||||
|
return finish(2);
|
||||||
|
|
||||||
|
while (m_url == url) {
|
||||||
|
lock.unlock();
|
||||||
|
error = av_read_frame(format_context, packet);
|
||||||
|
lock.lock();
|
||||||
|
if (m_url != url)
|
||||||
|
break;
|
||||||
|
if (error < 0)
|
||||||
|
break;
|
||||||
|
if (packet->stream_index == video_stream) {
|
||||||
|
const int decode_error = decoder.decode(*packet);
|
||||||
|
if (decode_error == 0) {
|
||||||
|
auto frame_size = m_frame_size;
|
||||||
|
lock.unlock();
|
||||||
|
#ifdef _WIN32
|
||||||
|
wxBitmap frame;
|
||||||
|
decoder.toWxBitmap(frame, frame_size);
|
||||||
|
#else
|
||||||
|
wxImage frame;
|
||||||
|
decoder.toWxImage(frame, frame_size);
|
||||||
|
#endif
|
||||||
|
lock.lock();
|
||||||
|
if (m_url != url)
|
||||||
|
break;
|
||||||
|
if (frame.IsOk())
|
||||||
|
m_frame = frame;
|
||||||
|
CallAfter([this] { Refresh(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
av_packet_unref(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
av_packet_free(&packet);
|
||||||
|
return finish(m_url == url ? 2 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
void wxMediaCtrl3::PlayThread()
|
void wxMediaCtrl3::PlayThread()
|
||||||
{
|
{
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
@@ -197,42 +300,21 @@ void wxMediaCtrl3::PlayThread()
|
|||||||
continue;
|
continue;
|
||||||
if (!url->HasScheme())
|
if (!url->HasScheme())
|
||||||
break;
|
break;
|
||||||
lk.unlock();
|
const wxString scheme = url->GetScheme();
|
||||||
Bambu_Tunnel tunnel = nullptr;
|
const bool generic_rtsp = scheme.CmpNoCase("rtsp") == 0 || scheme.CmpNoCase("rtsps") == 0;
|
||||||
int error = Bambu_Create(&tunnel, m_url->BuildURI().ToUTF8());
|
int error = 0;
|
||||||
if (error == 0) {
|
if (generic_rtsp) {
|
||||||
Bambu_SetLogger(tunnel, &wxMediaCtrl3::bambu_log, this);
|
error = PlayRtsp(url, lk);
|
||||||
error = Bambu_Open(tunnel);
|
} else {
|
||||||
if (error == 0)
|
lk.unlock();
|
||||||
error = Bambu_would_block;
|
Bambu_Tunnel tunnel = nullptr;
|
||||||
}
|
error = Bambu_Create(&tunnel, m_url->BuildURI().ToUTF8());
|
||||||
lk.lock();
|
if (error == 0) {
|
||||||
while (error == int(Bambu_would_block)) {
|
Bambu_SetLogger(tunnel, &wxMediaCtrl3::bambu_log, this);
|
||||||
m_cond.wait_for(lk, 100ms);
|
error = Bambu_Open(tunnel);
|
||||||
if (m_url != url) {
|
if (error == 0)
|
||||||
error = 1;
|
error = Bambu_would_block;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
lk.unlock();
|
|
||||||
error = Bambu_StartStream(tunnel, true);
|
|
||||||
lk.lock();
|
|
||||||
}
|
|
||||||
Bambu_StreamInfo info;
|
|
||||||
if (error == 0)
|
|
||||||
error = Bambu_GetStreamInfo(tunnel, 0, &info);
|
|
||||||
AVVideoDecoder decoder;
|
|
||||||
int minFrameDuration = 0;
|
|
||||||
if (error == 0) {
|
|
||||||
decoder.open(info);
|
|
||||||
m_video_size = { info.format.video.width, info.format.video.height };
|
|
||||||
adjust_frame_size(m_frame_size, m_video_size, GetSize());
|
|
||||||
minFrameDuration = 800 / info.format.video.frame_rate; // 80%
|
|
||||||
NotifyStopped();
|
|
||||||
}
|
|
||||||
Bambu_Sample sample;
|
|
||||||
while (error == 0) {
|
|
||||||
lk.unlock();
|
|
||||||
error = Bambu_ReadSample(tunnel, &sample);
|
|
||||||
lk.lock();
|
lk.lock();
|
||||||
while (error == int(Bambu_would_block)) {
|
while (error == int(Bambu_would_block)) {
|
||||||
m_cond.wait_for(lk, 100ms);
|
m_cond.wait_for(lk, 100ms);
|
||||||
@@ -240,59 +322,87 @@ void wxMediaCtrl3::PlayThread()
|
|||||||
error = 1;
|
error = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
lk.unlock();
|
||||||
|
error = Bambu_StartStream(tunnel, true);
|
||||||
|
lk.lock();
|
||||||
|
}
|
||||||
|
Bambu_StreamInfo info;
|
||||||
|
if (error == 0)
|
||||||
|
error = Bambu_GetStreamInfo(tunnel, 0, &info);
|
||||||
|
AVVideoDecoder decoder;
|
||||||
|
int minFrameDuration = 0;
|
||||||
|
if (error == 0) {
|
||||||
|
decoder.open(info);
|
||||||
|
m_video_size = { info.format.video.width, info.format.video.height };
|
||||||
|
adjust_frame_size(m_frame_size, m_video_size, GetSize());
|
||||||
|
minFrameDuration = 800 / info.format.video.frame_rate; // 80%
|
||||||
|
NotifyStopped();
|
||||||
|
}
|
||||||
|
Bambu_Sample sample;
|
||||||
|
while (error == 0) {
|
||||||
lk.unlock();
|
lk.unlock();
|
||||||
error = Bambu_ReadSample(tunnel, &sample);
|
error = Bambu_ReadSample(tunnel, &sample);
|
||||||
lk.lock();
|
lk.lock();
|
||||||
}
|
while (error == int(Bambu_would_block)) {
|
||||||
if (error == 0) {
|
m_cond.wait_for(lk, 100ms);
|
||||||
auto frame_size = m_frame_size;
|
if (m_url != url) {
|
||||||
lk.unlock();
|
error = 1;
|
||||||
decoder.decode(sample);
|
break;
|
||||||
#ifdef _WIN32
|
|
||||||
wxBitmap bm;
|
|
||||||
decoder.toWxBitmap(bm, frame_size);
|
|
||||||
#else
|
|
||||||
wxImage bm;
|
|
||||||
decoder.toWxImage(bm, frame_size);
|
|
||||||
#endif
|
|
||||||
lk.lock();
|
|
||||||
if (m_url != url) {
|
|
||||||
error = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (bm.IsOk()) {
|
|
||||||
auto now = std::chrono::system_clock::now();
|
|
||||||
if (m_last_PTS && (sample.decode_time - m_last_PTS) < 30000000ULL) { // 3s
|
|
||||||
auto next_PTS_expected = m_last_PTS_expected + std::chrono::milliseconds((sample.decode_time - m_last_PTS) / 10000ULL);
|
|
||||||
// The frame is late, catch up a little
|
|
||||||
auto next_PTS_practical = m_last_PTS_practical + std::chrono::milliseconds(minFrameDuration);
|
|
||||||
auto next_PTS = std::max(next_PTS_expected, next_PTS_practical);
|
|
||||||
if(now < next_PTS)
|
|
||||||
std::this_thread::sleep_until(next_PTS);
|
|
||||||
else
|
|
||||||
next_PTS = now;
|
|
||||||
//auto text = wxString::Format(L"wxMediaCtrl3 pts diff %ld\n", std::chrono::duration_cast<std::chrono::milliseconds>(next_PTS - next_PTS_expected).count());
|
|
||||||
//OutputDebugString(text);
|
|
||||||
m_last_PTS = sample.decode_time;
|
|
||||||
m_last_PTS_expected = next_PTS_expected;
|
|
||||||
m_last_PTS_practical = next_PTS;
|
|
||||||
} else {
|
|
||||||
// Resync
|
|
||||||
m_last_PTS = sample.decode_time;
|
|
||||||
m_last_PTS_expected = now;
|
|
||||||
m_last_PTS_practical = now;
|
|
||||||
}
|
}
|
||||||
m_frame = bm;
|
lk.unlock();
|
||||||
|
error = Bambu_ReadSample(tunnel, &sample);
|
||||||
|
lk.lock();
|
||||||
|
}
|
||||||
|
if (error == 0) {
|
||||||
|
auto frame_size = m_frame_size;
|
||||||
|
lk.unlock();
|
||||||
|
decoder.decode(sample);
|
||||||
|
#ifdef _WIN32
|
||||||
|
wxBitmap bm;
|
||||||
|
decoder.toWxBitmap(bm, frame_size);
|
||||||
|
#else
|
||||||
|
wxImage bm;
|
||||||
|
decoder.toWxImage(bm, frame_size);
|
||||||
|
#endif
|
||||||
|
lk.lock();
|
||||||
|
if (m_url != url) {
|
||||||
|
error = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (bm.IsOk()) {
|
||||||
|
auto now = std::chrono::system_clock::now();
|
||||||
|
if (m_last_PTS && (sample.decode_time - m_last_PTS) < 30000000ULL) { // 3s
|
||||||
|
auto next_PTS_expected = m_last_PTS_expected + std::chrono::milliseconds((sample.decode_time - m_last_PTS) / 10000ULL);
|
||||||
|
// The frame is late, catch up a little
|
||||||
|
auto next_PTS_practical = m_last_PTS_practical + std::chrono::milliseconds(minFrameDuration);
|
||||||
|
auto next_PTS = std::max(next_PTS_expected, next_PTS_practical);
|
||||||
|
if(now < next_PTS)
|
||||||
|
std::this_thread::sleep_until(next_PTS);
|
||||||
|
else
|
||||||
|
next_PTS = now;
|
||||||
|
//auto text = wxString::Format(L"wxMediaCtrl3 pts diff %ld\n", std::chrono::duration_cast<std::chrono::milliseconds>(next_PTS - next_PTS_expected).count());
|
||||||
|
//OutputDebugString(text);
|
||||||
|
m_last_PTS = sample.decode_time;
|
||||||
|
m_last_PTS_expected = next_PTS_expected;
|
||||||
|
m_last_PTS_practical = next_PTS;
|
||||||
|
} else {
|
||||||
|
// Resync
|
||||||
|
m_last_PTS = sample.decode_time;
|
||||||
|
m_last_PTS_expected = now;
|
||||||
|
m_last_PTS_practical = now;
|
||||||
|
}
|
||||||
|
m_frame = bm;
|
||||||
|
}
|
||||||
|
CallAfter([this] { Refresh(); });
|
||||||
}
|
}
|
||||||
CallAfter([this] { Refresh(); });
|
|
||||||
}
|
}
|
||||||
}
|
if (tunnel) {
|
||||||
if (tunnel) {
|
lk.unlock();
|
||||||
lk.unlock();
|
Bambu_Close(tunnel);
|
||||||
Bambu_Close(tunnel);
|
Bambu_Destroy(tunnel);
|
||||||
Bambu_Destroy(tunnel);
|
tunnel = nullptr;
|
||||||
tunnel = nullptr;
|
lk.lock();
|
||||||
lk.lock();
|
}
|
||||||
}
|
}
|
||||||
if (m_url == url)
|
if (m_url == url)
|
||||||
m_error = error;
|
m_error = error;
|
||||||
|
|||||||
@@ -56,8 +56,10 @@ protected:
|
|||||||
void DoSetSize(int x, int y, int width, int height, int sizeFlags) override;
|
void DoSetSize(int x, int y, int width, int height, int sizeFlags) override;
|
||||||
|
|
||||||
static void bambu_log(void *ctx, int level, tchar const *msg);
|
static void bambu_log(void *ctx, int level, tchar const *msg);
|
||||||
|
static int rtsp_interrupt_callback(void *opaque);
|
||||||
|
|
||||||
void PlayThread();
|
void PlayThread();
|
||||||
|
int PlayRtsp(std::shared_ptr<wxURI> const &url, std::unique_lock<std::mutex> &lock);
|
||||||
|
|
||||||
void NotifyStopped();
|
void NotifyStopped();
|
||||||
|
|
||||||
@@ -74,6 +76,7 @@ private:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::shared_ptr<wxURI> m_url;
|
std::shared_ptr<wxURI> m_url;
|
||||||
|
std::shared_ptr<wxURI> m_active_url;
|
||||||
std::uint64_t m_last_PTS{0};
|
std::uint64_t m_last_PTS{0};
|
||||||
std::chrono::system_clock::time_point m_last_PTS_expected;
|
std::chrono::system_clock::time_point m_last_PTS_expected;
|
||||||
std::chrono::system_clock::time_point m_last_PTS_practical;
|
std::chrono::system_clock::time_point m_last_PTS_practical;
|
||||||
|
|||||||
@@ -81,6 +81,14 @@ enum class FilamentSyncMode {
|
|||||||
pull ///< On-demand fetch via REST API (blocking call)
|
pull ///< On-demand fetch via REST API (blocking call)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class CameraStreamMode {
|
||||||
|
none = 0,
|
||||||
|
http, // LAN or Cloud
|
||||||
|
rtsp, // LAN only
|
||||||
|
webrtc, // Cloud only
|
||||||
|
http_snapshot // HTTP endpoint returning one image per request
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IPrinterAgent - Interface for printer operations.
|
* IPrinterAgent - Interface for printer operations.
|
||||||
*
|
*
|
||||||
@@ -354,12 +362,26 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual FilamentSyncMode get_filament_sync_mode() const { return FilamentSyncMode::none; }
|
virtual FilamentSyncMode get_filament_sync_mode() const { return FilamentSyncMode::none; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the camera stream mode for this agent. This value can be deterministic and derived at
|
||||||
|
* runtime if the printer supports multiple camera stream modes. E.g. LAN => HTTP/RTSP, Cloud => WebRTC.
|
||||||
|
*
|
||||||
|
* @return CameraStreamMode indicating how the camera stream is obtained or used:
|
||||||
|
*/
|
||||||
|
virtual CameraStreamMode get_camera_stream_mode() const { return CameraStreamMode::none; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh filament info from the printer synchronously.
|
* Refresh filament info from the printer synchronously.
|
||||||
* Should only be called when get_filament_sync_mode() returns FilamentSyncMode::pull.
|
* Should only be called when get_filament_sync_mode() returns FilamentSyncMode::pull.
|
||||||
* Populates the MachineObject's DevFilaSystem with fetched filament data.
|
* Populates the MachineObject's DevFilaSystem with fetched filament data.
|
||||||
*/
|
*/
|
||||||
virtual bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) { return false; }
|
virtual bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) { return false; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current camera stream URL for this agent's active machine.
|
||||||
|
* Only meaningful when get_camera_stream_mode() returns an HTTP or RTSP mode.
|
||||||
|
*/
|
||||||
|
virtual std::string get_camera_url() const { return {}; }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|||||||
@@ -750,6 +750,57 @@ bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSync
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CameraStreamMode MoonrakerPrinterAgent::get_camera_stream_mode() const
|
||||||
|
{
|
||||||
|
refresh_webcam_info();
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(payload_mutex);
|
||||||
|
return webcam_stream_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MoonrakerPrinterAgent::get_camera_url() const
|
||||||
|
{
|
||||||
|
refresh_webcam_info();
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(payload_mutex);
|
||||||
|
return webcam_stream_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MoonrakerPrinterAgent::refresh_webcam_info() const
|
||||||
|
{
|
||||||
|
std::string base_url;
|
||||||
|
std::string api_key;
|
||||||
|
uint64_t generation;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(connect_mutex);
|
||||||
|
base_url = device_info.base_url;
|
||||||
|
api_key = device_info.api_key;
|
||||||
|
generation = connect_generation.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint64_t now_ms = static_cast<uint64_t>(
|
||||||
|
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count());
|
||||||
|
|
||||||
|
if (base_url.empty()) {
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(payload_mutex);
|
||||||
|
webcam_stream_url.clear();
|
||||||
|
webcam_stream_mode = CameraStreamMode::none;
|
||||||
|
webcam_info_last_lookup_ms = 0;
|
||||||
|
webcam_info_generation = generation;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(payload_mutex);
|
||||||
|
if (webcam_info_generation == generation &&
|
||||||
|
now_ms - webcam_info_last_lookup_ms < WEBCAM_INFO_REFRESH_INTERVAL_MS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
webcam_info_generation = generation;
|
||||||
|
webcam_info_last_lookup_ms = now_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch_webcam_info(base_url, api_key, generation);
|
||||||
|
}
|
||||||
|
|
||||||
std::string MoonrakerPrinterAgent::trim_and_upper(const std::string& input)
|
std::string MoonrakerPrinterAgent::trim_and_upper(const std::string& input)
|
||||||
{
|
{
|
||||||
std::string result = input;
|
std::string result = input;
|
||||||
@@ -1605,10 +1656,11 @@ bool MoonrakerPrinterAgent::send_ws_rpc(const std::string& method, const nlohman
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MoonrakerPrinterAgent::fetch_webcam_info(const std::string& base_url, const std::string& api_key, uint64_t generation)
|
bool MoonrakerPrinterAgent::fetch_webcam_info(const std::string& base_url, const std::string& api_key, uint64_t generation) const
|
||||||
{
|
{
|
||||||
std::string stream_url;
|
std::string camera_url;
|
||||||
std::string webcam_name;
|
std::string webcam_name;
|
||||||
|
CameraStreamMode stream_mode = CameraStreamMode::none;
|
||||||
std::string error;
|
std::string error;
|
||||||
try {
|
try {
|
||||||
std::string response_body;
|
std::string response_body;
|
||||||
@@ -1649,16 +1701,24 @@ bool MoonrakerPrinterAgent::fetch_webcam_info(const std::string& base_url, const
|
|||||||
error = "Unexpected JSON structure";
|
error = "Unexpected JSON structure";
|
||||||
} else {
|
} else {
|
||||||
for (const auto& webcam : result["webcams"]) {
|
for (const auto& webcam : result["webcams"]) {
|
||||||
if (webcam.is_object() && webcam.value("enabled", false) && webcam.contains("stream_url") &&
|
if (webcam.is_object() && webcam.value("enabled", false)) {
|
||||||
webcam["stream_url"].is_string()) {
|
if (webcam.contains("stream_url") && webcam["stream_url"].is_string() &&
|
||||||
stream_url = webcam["stream_url"].get<std::string>();
|
!webcam["stream_url"].get<std::string>().empty()) {
|
||||||
|
camera_url = webcam["stream_url"].get<std::string>();
|
||||||
|
stream_mode = CameraStreamMode::http;
|
||||||
|
} else if (webcam.contains("snapshot_url") && webcam["snapshot_url"].is_string() &&
|
||||||
|
!webcam["snapshot_url"].get<std::string>().empty()) {
|
||||||
|
camera_url = webcam["snapshot_url"].get<std::string>();
|
||||||
|
stream_mode = CameraStreamMode::http_snapshot;
|
||||||
|
}
|
||||||
if (webcam.contains("name") && webcam["name"].is_string()) {
|
if (webcam.contains("name") && webcam["name"].is_string()) {
|
||||||
webcam_name = webcam["name"].get<std::string>();
|
webcam_name = webcam["name"].get<std::string>();
|
||||||
}
|
}
|
||||||
break;
|
if (!camera_url.empty())
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (stream_url.empty()) {
|
if (camera_url.empty()) {
|
||||||
error = "No enabled webcam";
|
error = "No enabled webcam";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1666,8 +1726,8 @@ bool MoonrakerPrinterAgent::fetch_webcam_info(const std::string& base_url, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (error.empty()) {
|
if (error.empty()) {
|
||||||
if (stream_url.rfind("http", 0) != 0 && !stream_url.empty() && stream_url.front() == '/') {
|
if (camera_url.rfind("http", 0) != 0 && !camera_url.empty() && camera_url.front() == '/') {
|
||||||
// why: Moonraker's API port serves a JSON 404 for /webcam; relative streams use the printer web root.
|
// why: Moonraker's API port serves a JSON 404 for /webcam; relative camera URLs use the printer web root.
|
||||||
const size_t scheme_end = base_url.find("://");
|
const size_t scheme_end = base_url.find("://");
|
||||||
const size_t authority_start = scheme_end == std::string::npos ? 0 : scheme_end + 3;
|
const size_t authority_start = scheme_end == std::string::npos ? 0 : scheme_end + 3;
|
||||||
const size_t authority_end = base_url.find('/', authority_start);
|
const size_t authority_end = base_url.find('/', authority_start);
|
||||||
@@ -1678,9 +1738,9 @@ bool MoonrakerPrinterAgent::fetch_webcam_info(const std::string& base_url, const
|
|||||||
std::all_of(authority.begin() + port_start + 1, authority.end(), [](char c) { return c >= '0' && c <= '9'; })) {
|
std::all_of(authority.begin() + port_start + 1, authority.end(), [](char c) { return c >= '0' && c <= '9'; })) {
|
||||||
authority.erase(port_start);
|
authority.erase(port_start);
|
||||||
}
|
}
|
||||||
stream_url = scheme + authority + stream_url;
|
camera_url = scheme + authority + camera_url;
|
||||||
} else if (stream_url.rfind("http", 0) != 0) {
|
} else if (camera_url.rfind("http", 0) != 0) {
|
||||||
error = "Unsupported webcam stream URL";
|
error = "Unsupported webcam URL";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
@@ -1692,14 +1752,15 @@ bool MoonrakerPrinterAgent::fetch_webcam_info(const std::string& base_url, const
|
|||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(payload_mutex);
|
std::lock_guard<std::recursive_mutex> lock(payload_mutex);
|
||||||
if (generation == connect_generation.load()) {
|
if (generation == connect_generation.load()) {
|
||||||
webcam_stream_url = error.empty() ? stream_url : "";
|
webcam_stream_url = error.empty() ? camera_url : "";
|
||||||
|
webcam_stream_mode = error.empty() ? stream_mode : CameraStreamMode::none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!error.empty()) {
|
if (!error.empty()) {
|
||||||
BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: webcam discovery failed: " << error;
|
BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: webcam discovery failed: " << error;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: selected webcam '" << webcam_name << "' with stream URL " << stream_url;
|
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: selected webcam '" << webcam_name << "' with camera URL " << camera_url;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2880,8 +2941,6 @@ void MoonrakerPrinterAgent::perform_connection_async(const std::string& dev_id,
|
|||||||
BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: Initial status query failed: " << error_msg;
|
BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: Initial status query failed: " << error_msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch_webcam_info(base_url, api_key, generation);
|
|
||||||
|
|
||||||
// Start WebSocket status stream
|
// Start WebSocket status stream
|
||||||
start_status_stream(dev_id, base_url, api_key);
|
start_status_stream(dev_id, base_url, api_key);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -73,10 +73,9 @@ public:
|
|||||||
int set_on_local_connect_fn(OnLocalConnectedFn fn) override;
|
int set_on_local_connect_fn(OnLocalConnectedFn fn) override;
|
||||||
int set_on_local_message_fn(OnMessageFn fn) override;
|
int set_on_local_message_fn(OnMessageFn fn) override;
|
||||||
int set_queue_on_main_fn(QueueOnMainFn fn) override;
|
int set_queue_on_main_fn(QueueOnMainFn fn) override;
|
||||||
|
|
||||||
// Pull-mode agent (on-demand filament sync)
|
|
||||||
FilamentSyncMode get_filament_sync_mode() const override { return FilamentSyncMode::pull; }
|
|
||||||
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) override;
|
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) override;
|
||||||
|
CameraStreamMode get_camera_stream_mode() const override;
|
||||||
|
std::string get_camera_url() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
struct MoonrakerDeviceInfo
|
struct MoonrakerDeviceInfo
|
||||||
@@ -153,7 +152,6 @@ private:
|
|||||||
|
|
||||||
bool fetch_object_list(const std::string& base_url, const std::string& api_key, std::set<std::string>& objects, std::string& error) const;
|
bool fetch_object_list(const std::string& base_url, const std::string& api_key, std::set<std::string>& objects, std::string& error) const;
|
||||||
bool query_printer_status(const std::string& base_url, const std::string& api_key, nlohmann::json& status, std::string& error) const;
|
bool query_printer_status(const std::string& base_url, const std::string& api_key, nlohmann::json& status, std::string& error) const;
|
||||||
bool fetch_webcam_info(const std::string& base_url, const std::string& api_key, uint64_t generation);
|
|
||||||
|
|
||||||
void announce_printhost_device();
|
void announce_printhost_device();
|
||||||
void dispatch_local_connect(int state, const std::string& dev_id, const std::string& msg);
|
void dispatch_local_connect(int state, const std::string& dev_id, const std::string& msg);
|
||||||
@@ -188,6 +186,12 @@ private:
|
|||||||
const std::string& api_key,
|
const std::string& api_key,
|
||||||
uint64_t generation);
|
uint64_t generation);
|
||||||
|
|
||||||
|
// why: a printer with no /server/webcams/list entry can still name its stream directly;
|
||||||
|
// subclasses (e.g. printers with a fixed webcam path) can override this instead.
|
||||||
|
virtual std::string webcam_stream_override(const std::string& base_url) const { return {}; }
|
||||||
|
void refresh_webcam_info() const;
|
||||||
|
bool fetch_webcam_info(const std::string& base_url, const std::string& api_key, uint64_t generation) const;
|
||||||
|
|
||||||
// System-specific filament fetch methods
|
// System-specific filament fetch methods
|
||||||
bool fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index);
|
bool fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index);
|
||||||
bool fetch_moonraker_filament_data(std::vector<AmsTrayData>& trays, int& max_lane_index);
|
bool fetch_moonraker_filament_data(std::vector<AmsTrayData>& trays, int& max_lane_index);
|
||||||
@@ -219,9 +223,14 @@ private:
|
|||||||
// note: guarded by payload_mutex; filled by refresh_thumbnail_url(), empty url = looked up, none found
|
// note: guarded by payload_mutex; filled by refresh_thumbnail_url(), empty url = looked up, none found
|
||||||
std::string thumbnail_filename;
|
std::string thumbnail_filename;
|
||||||
std::string thumbnail_url;
|
std::string thumbnail_url;
|
||||||
std::string webcam_stream_url;
|
mutable std::string webcam_stream_url;
|
||||||
|
mutable CameraStreamMode webcam_stream_mode = CameraStreamMode::none;
|
||||||
|
mutable uint64_t webcam_info_last_lookup_ms = 0;
|
||||||
|
mutable uint64_t webcam_info_generation = 0;
|
||||||
unsigned thumbnail_lookup_attempts = 0;
|
unsigned thumbnail_lookup_attempts = 0;
|
||||||
|
|
||||||
|
static constexpr uint64_t WEBCAM_INFO_REFRESH_INTERVAL_MS = 1000;
|
||||||
|
|
||||||
std::atomic<int> next_jsonrpc_id{1};
|
std::atomic<int> next_jsonrpc_id{1};
|
||||||
std::set<std::string> available_objects; // Track for feature detection
|
std::set<std::string> available_objects; // Track for feature detection
|
||||||
bool assumed_light_on = false;
|
bool assumed_light_on = false;
|
||||||
|
|||||||
@@ -1008,6 +1008,20 @@ bool NetworkAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode sync
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CameraStreamMode NetworkAgent::get_camera_stream_mode() const
|
||||||
|
{
|
||||||
|
if (m_printer_agent)
|
||||||
|
return m_printer_agent->get_camera_stream_mode();
|
||||||
|
return CameraStreamMode::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string NetworkAgent::get_local_camera_stream_url() const
|
||||||
|
{
|
||||||
|
if (m_printer_agent)
|
||||||
|
return m_printer_agent->get_camera_url();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
int NetworkAgent::request_bind_ticket(std::string* ticket)
|
int NetworkAgent::request_bind_ticket(std::string* ticket)
|
||||||
{
|
{
|
||||||
if (m_printer_agent)
|
if (m_printer_agent)
|
||||||
|
|||||||
@@ -181,6 +181,8 @@ public:
|
|||||||
int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
|
int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
|
||||||
FilamentSyncMode get_filament_sync_mode() const;
|
FilamentSyncMode get_filament_sync_mode() const;
|
||||||
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull);
|
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull);
|
||||||
|
CameraStreamMode get_camera_stream_mode() const;
|
||||||
|
std::string get_local_camera_stream_url() const;
|
||||||
int request_bind_ticket(std::string* ticket);
|
int request_bind_ticket(std::string* ticket);
|
||||||
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback);
|
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback);
|
||||||
|
|
||||||
|
|||||||
@@ -96,15 +96,6 @@ void SnapmakerPrinterAgent::on_status_loop_tick(const std::string& dev_id)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int SnapmakerPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl)
|
|
||||||
{
|
|
||||||
const int rtn = MoonrakerPrinterAgent::connect_printer(dev_id, dev_ip, username, password, use_ssl);
|
|
||||||
if (rtn == BAMBU_NETWORK_SUCCESS) {
|
|
||||||
start_camera_monitor();
|
|
||||||
}
|
|
||||||
return rtn;
|
|
||||||
}
|
|
||||||
|
|
||||||
int SnapmakerPrinterAgent::command_start_camera(std::string dev_id)
|
int SnapmakerPrinterAgent::command_start_camera(std::string dev_id)
|
||||||
{
|
{
|
||||||
(void) dev_id;
|
(void) dev_id;
|
||||||
@@ -149,127 +140,139 @@ std::string SnapmakerPrinterAgent::combine_filament_type(const std::string& type
|
|||||||
|
|
||||||
bool SnapmakerPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode)
|
bool SnapmakerPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode)
|
||||||
{
|
{
|
||||||
|
(void) dev_id;
|
||||||
if (sync_mode != get_filament_sync_mode())
|
if (sync_mode != get_filament_sync_mode())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
std::string url = join_url(device_info.base_url, "/printer/objects/query?print_task_config&filament_detect");
|
const std::string base_url = device_info.base_url;
|
||||||
|
const std::string api_key = device_info.api_key;
|
||||||
|
|
||||||
std::string response_body;
|
filament_fetch_in_flight.fetch_add(1, std::memory_order_relaxed);
|
||||||
bool success = false;
|
|
||||||
std::string http_error;
|
|
||||||
|
|
||||||
auto http = Http::get(url);
|
std::thread([this, base_url, api_key]() {
|
||||||
if (!device_info.api_key.empty()) {
|
struct InFlightGuard
|
||||||
http.header("X-Api-Key", device_info.api_key);
|
{
|
||||||
}
|
std::atomic<int>& counter;
|
||||||
http.timeout_connect(5)
|
~InFlightGuard() { counter.fetch_sub(1, std::memory_order_relaxed); }
|
||||||
.timeout_max(10)
|
} guard{filament_fetch_in_flight};
|
||||||
.on_complete([&](std::string body, unsigned status) {
|
|
||||||
if (status == 200) {
|
|
||||||
response_body = body;
|
|
||||||
success = true;
|
|
||||||
} else {
|
|
||||||
http_error = "HTTP error: " + std::to_string(status);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on_error([&](std::string body, std::string err, unsigned status) {
|
|
||||||
http_error = err;
|
|
||||||
if (status > 0) {
|
|
||||||
http_error += " (HTTP " + std::to_string(status) + ")";
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.perform_sync();
|
|
||||||
|
|
||||||
if (!success) {
|
const std::string url = join_url(base_url, "/printer/objects/query?print_task_config&filament_detect");
|
||||||
BOOST_LOG_TRIVIAL(warning) << "SnapmakerPrinterAgent::fetch_filament_info: HTTP request failed: " << http_error;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto json = nlohmann::json::parse(response_body, nullptr, false, true);
|
std::string response_body;
|
||||||
if (json.is_discarded()) {
|
bool success = false;
|
||||||
BOOST_LOG_TRIVIAL(warning) << "SnapmakerPrinterAgent::fetch_filament_info: Invalid JSON response";
|
std::string http_error;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Navigate to result.status.print_task_config
|
auto http = Http::get(url);
|
||||||
if (!json.contains("result") || !json["result"].contains("status") ||
|
if (!api_key.empty()) {
|
||||||
!json["result"]["status"].contains("print_task_config")) {
|
http.header("X-Api-Key", api_key);
|
||||||
BOOST_LOG_TRIVIAL(warning) << "SnapmakerPrinterAgent::fetch_filament_info: Missing print_task_config in response";
|
}
|
||||||
return false;
|
http.timeout_connect(5)
|
||||||
}
|
.timeout_max(10)
|
||||||
|
.on_complete([&](std::string body, unsigned status) {
|
||||||
auto& ptc = json["result"]["status"]["print_task_config"];
|
if (status == 200) {
|
||||||
|
response_body = body;
|
||||||
// Read parallel arrays from print_task_config
|
success = true;
|
||||||
auto filament_exist = ptc.value("filament_exist", std::vector<bool>{});
|
|
||||||
auto filament_type = ptc.value("filament_type", std::vector<std::string>{});
|
|
||||||
auto filament_sub_type = ptc.value("filament_sub_type", std::vector<std::string>{});
|
|
||||||
auto filament_color = ptc.value("filament_color_rgba", std::vector<std::string>{});
|
|
||||||
auto filament_vendor = ptc.value("filament_vendor", std::vector<std::string>{});
|
|
||||||
|
|
||||||
const int slot_count = static_cast<int>(filament_exist.size());
|
|
||||||
if (slot_count == 0) {
|
|
||||||
BOOST_LOG_TRIVIAL(info) << "SnapmakerPrinterAgent::fetch_filament_info: No filament slots reported";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read NFC filament_detect data for temperature info (optional)
|
|
||||||
nlohmann::json nfc_info;
|
|
||||||
if (json["result"]["status"].contains("filament_detect") &&
|
|
||||||
json["result"]["status"]["filament_detect"].contains("info")) {
|
|
||||||
nfc_info = json["result"]["status"]["filament_detect"]["info"];
|
|
||||||
}
|
|
||||||
|
|
||||||
static const std::string empty_str;
|
|
||||||
static const std::string default_color = "FFFFFFFF";
|
|
||||||
|
|
||||||
std::vector<AmsTrayData> trays;
|
|
||||||
trays.reserve(slot_count);
|
|
||||||
|
|
||||||
for (int i = 0; i < slot_count; ++i) {
|
|
||||||
AmsTrayData tray;
|
|
||||||
tray.slot_index = i;
|
|
||||||
tray.has_filament = filament_exist[i];
|
|
||||||
|
|
||||||
if (tray.has_filament) {
|
|
||||||
tray.tray_type = combine_filament_type(safe_at(filament_type, i, empty_str),
|
|
||||||
safe_at(filament_sub_type, i, empty_str));
|
|
||||||
tray.tray_color = safe_at(filament_color, i, default_color);
|
|
||||||
|
|
||||||
auto* bundle = GUI::wxGetApp().preset_bundle;
|
|
||||||
// Try to find a matching preset for this filament based on vendor, type and color.
|
|
||||||
// If not found, default to traditional search by type only or generic type mapping.
|
|
||||||
if (bundle) {
|
|
||||||
std::string vendor = safe_at(filament_vendor, i, empty_str);
|
|
||||||
std::string filament_id = find_closest_color_preset_by_vendor_and_type(bundle->filaments, vendor, tray.tray_type,
|
|
||||||
tray.tray_color);
|
|
||||||
|
|
||||||
if (!filament_id.empty()) {
|
|
||||||
tray.tray_info_idx = filament_id;
|
|
||||||
BOOST_LOG_TRIVIAL(warning) << "Filament sync: Found manufacturer-specific profile for slot " << i << ": "
|
|
||||||
<< filament_id;
|
|
||||||
} else {
|
} else {
|
||||||
tray.tray_info_idx = bundle->filaments.filament_id_by_type(tray.tray_type);
|
http_error = "HTTP error: " + std::to_string(status);
|
||||||
}
|
}
|
||||||
} else {
|
})
|
||||||
tray.tray_info_idx = map_filament_type_to_generic_id(tray.tray_type);
|
.on_error([&](std::string body, std::string err, unsigned status) {
|
||||||
}
|
http_error = err;
|
||||||
|
if (status > 0) {
|
||||||
|
http_error += " (HTTP " + std::to_string(status) + ")";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
// Extract NFC temperature data if available
|
if (!success) {
|
||||||
if (nfc_info.is_array() && i < static_cast<int>(nfc_info.size()) && nfc_info[i].is_object()) {
|
BOOST_LOG_TRIVIAL(warning) << "SnapmakerPrinterAgent::fetch_filament_info: HTTP request failed: " << http_error;
|
||||||
auto& nfc_slot = nfc_info[i];
|
return;
|
||||||
std::string vendor = nfc_slot.value("VENDOR", "NONE");
|
|
||||||
if (vendor != "NONE" && !vendor.empty()) {
|
|
||||||
tray.bed_temp = nfc_slot.value("BED_TEMP", 0);
|
|
||||||
tray.nozzle_temp = nfc_slot.value("FIRST_LAYER_TEMP", 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trays.emplace_back(std::move(tray));
|
auto json = nlohmann::json::parse(response_body, nullptr, false, true);
|
||||||
}
|
if (json.is_discarded()) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "SnapmakerPrinterAgent::fetch_filament_info: Invalid JSON response";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigate to result.status.print_task_config
|
||||||
|
if (!json.contains("result") || !json["result"].contains("status") || !json["result"]["status"].contains("print_task_config")) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "SnapmakerPrinterAgent::fetch_filament_info: Missing print_task_config in response";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& ptc = json["result"]["status"]["print_task_config"];
|
||||||
|
|
||||||
|
// Read parallel arrays from print_task_config
|
||||||
|
auto filament_exist = ptc.value("filament_exist", std::vector<bool>{});
|
||||||
|
auto filament_type = ptc.value("filament_type", std::vector<std::string>{});
|
||||||
|
auto filament_sub_type = ptc.value("filament_sub_type", std::vector<std::string>{});
|
||||||
|
auto filament_color = ptc.value("filament_color_rgba", std::vector<std::string>{});
|
||||||
|
auto filament_vendor = ptc.value("filament_vendor", std::vector<std::string>{});
|
||||||
|
|
||||||
|
const int slot_count = static_cast<int>(filament_exist.size());
|
||||||
|
if (slot_count == 0) {
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "SnapmakerPrinterAgent::fetch_filament_info: No filament slots reported";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read NFC filament_detect data for temperature info (optional)
|
||||||
|
nlohmann::json nfc_info;
|
||||||
|
if (json["result"]["status"].contains("filament_detect") && json["result"]["status"]["filament_detect"].contains("info")) {
|
||||||
|
nfc_info = json["result"]["status"]["filament_detect"]["info"];
|
||||||
|
}
|
||||||
|
|
||||||
|
static const std::string empty_str;
|
||||||
|
static const std::string default_color = "FFFFFFFF";
|
||||||
|
|
||||||
|
std::vector<AmsTrayData> trays;
|
||||||
|
trays.reserve(slot_count);
|
||||||
|
|
||||||
|
for (int i = 0; i < slot_count; ++i) {
|
||||||
|
AmsTrayData tray;
|
||||||
|
tray.slot_index = i;
|
||||||
|
tray.has_filament = filament_exist[i];
|
||||||
|
|
||||||
|
if (tray.has_filament) {
|
||||||
|
tray.tray_type = combine_filament_type(safe_at(filament_type, i, empty_str), safe_at(filament_sub_type, i, empty_str));
|
||||||
|
tray.tray_color = safe_at(filament_color, i, default_color);
|
||||||
|
|
||||||
|
auto* bundle = GUI::wxGetApp().preset_bundle;
|
||||||
|
// Try to find a matching preset for this filament based on vendor, type and color.
|
||||||
|
// If not found, default to traditional search by type only or generic type mapping.
|
||||||
|
if (bundle) {
|
||||||
|
std::string vendor = safe_at(filament_vendor, i, empty_str);
|
||||||
|
std::string filament_id = find_closest_color_preset_by_vendor_and_type(bundle->filaments, vendor, tray.tray_type,
|
||||||
|
tray.tray_color);
|
||||||
|
|
||||||
|
if (!filament_id.empty()) {
|
||||||
|
tray.tray_info_idx = filament_id;
|
||||||
|
BOOST_LOG_TRIVIAL(warning)
|
||||||
|
<< "Filament sync: Found manufacturer-specific profile for slot " << i << ": " << filament_id;
|
||||||
|
} else {
|
||||||
|
tray.tray_info_idx = bundle->filaments.filament_id_by_type(tray.tray_type);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tray.tray_info_idx = map_filament_type_to_generic_id(tray.tray_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract NFC temperature data if available
|
||||||
|
if (nfc_info.is_array() && i < static_cast<int>(nfc_info.size()) && nfc_info[i].is_object()) {
|
||||||
|
auto& nfc_slot = nfc_info[i];
|
||||||
|
std::string vendor = nfc_slot.value("VENDOR", "NONE");
|
||||||
|
if (vendor != "NONE" && !vendor.empty()) {
|
||||||
|
tray.bed_temp = nfc_slot.value("BED_TEMP", 0);
|
||||||
|
tray.nozzle_temp = nfc_slot.value("FIRST_LAYER_TEMP", 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trays.emplace_back(std::move(tray));
|
||||||
|
}
|
||||||
|
|
||||||
|
build_ams_payload(1, slot_count - 1, trays);
|
||||||
|
}).detach();
|
||||||
|
|
||||||
build_ams_payload(1, slot_count - 1, trays);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "IPrinterAgent.hpp"
|
||||||
#include "MoonrakerPrinterAgent.hpp"
|
#include "MoonrakerPrinterAgent.hpp"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@@ -21,6 +22,8 @@ public:
|
|||||||
FilamentSyncMode get_filament_sync_mode() const override;
|
FilamentSyncMode get_filament_sync_mode() const override;
|
||||||
int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override;
|
int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override;
|
||||||
int command_start_camera(std::string dev_id) override;
|
int command_start_camera(std::string dev_id) override;
|
||||||
|
CameraStreamMode get_camera_stream_mode() const override { return CameraStreamMode::http_snapshot; }
|
||||||
|
std::string get_camera_url() const override { return device_info.base_url + "/server/files/camera/monitor.jpg"; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Combine filament_type + filament_sub_type into a unified type string
|
// Combine filament_type + filament_sub_type into a unified type string
|
||||||
|
|||||||
@@ -23,6 +23,14 @@ void PrinterAgentPluginCapability::RegisterBindings(pybind11::module_& module)
|
|||||||
.value("Pull", FilamentSyncMode::pull)
|
.value("Pull", FilamentSyncMode::pull)
|
||||||
.export_values();
|
.export_values();
|
||||||
|
|
||||||
|
py::enum_<CameraStreamMode>(printer_agent_module, "CameraStreamMode")
|
||||||
|
.value("None_", CameraStreamMode::none)
|
||||||
|
.value("HTTP", CameraStreamMode::http)
|
||||||
|
.value("HTTP_SNAPSHOT", CameraStreamMode::http_snapshot)
|
||||||
|
.value("RTSP", CameraStreamMode::rtsp)
|
||||||
|
.value("WebRTC", CameraStreamMode::webrtc)
|
||||||
|
.export_values();
|
||||||
|
|
||||||
py::class_<AgentInfo>(printer_agent_module, "AgentInfo")
|
py::class_<AgentInfo>(printer_agent_module, "AgentInfo")
|
||||||
.def(py::init<>())
|
.def(py::init<>())
|
||||||
.def(py::init([](std::string id, std::string name, std::string version, std::string description) {
|
.def(py::init([](std::string id, std::string name, std::string version, std::string description) {
|
||||||
@@ -105,7 +113,9 @@ void PrinterAgentPluginCapability::RegisterBindings(pybind11::module_& module)
|
|||||||
.def("start_send_gcode_to_sdcard", &PrinterAgentPluginCapability::start_send_gcode_to_sdcard)
|
.def("start_send_gcode_to_sdcard", &PrinterAgentPluginCapability::start_send_gcode_to_sdcard)
|
||||||
.def("start_local_print", &PrinterAgentPluginCapability::start_local_print)
|
.def("start_local_print", &PrinterAgentPluginCapability::start_local_print)
|
||||||
.def("get_filament_sync_mode", &PrinterAgentPluginCapability::get_filament_sync_mode)
|
.def("get_filament_sync_mode", &PrinterAgentPluginCapability::get_filament_sync_mode)
|
||||||
|
.def("get_camera_stream_mode", &PrinterAgentPluginCapability::get_camera_stream_mode)
|
||||||
.def("fetch_filament_info", &PrinterAgentPluginCapability::fetch_filament_info)
|
.def("fetch_filament_info", &PrinterAgentPluginCapability::fetch_filament_info)
|
||||||
|
.def("get_camera_url", &PrinterAgentPluginCapability::get_camera_url)
|
||||||
.def("check_cert", &PrinterAgentPluginCapability::check_cert)
|
.def("check_cert", &PrinterAgentPluginCapability::check_cert)
|
||||||
.def("install_device_cert", &PrinterAgentPluginCapability::install_device_cert)
|
.def("install_device_cert", &PrinterAgentPluginCapability::install_device_cert)
|
||||||
.def("ping_bind", &PrinterAgentPluginCapability::ping_bind)
|
.def("ping_bind", &PrinterAgentPluginCapability::ping_bind)
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ public:
|
|||||||
int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override = 0;
|
int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override = 0;
|
||||||
FilamentSyncMode get_filament_sync_mode() const override = 0;
|
FilamentSyncMode get_filament_sync_mode() const override = 0;
|
||||||
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) override = 0;
|
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) override = 0;
|
||||||
|
CameraStreamMode get_camera_stream_mode() const override = 0;
|
||||||
|
std::string get_camera_url() const override = 0;
|
||||||
|
|
||||||
int check_cert() override = 0;
|
int check_cert() override = 0;
|
||||||
void install_device_cert(std::string dev_id, bool lan_only) override = 0;
|
void install_device_cert(std::string dev_id, bool lan_only) override = 0;
|
||||||
|
|||||||
@@ -5,7 +5,10 @@
|
|||||||
#include "../../PyPluginTrampoline.hpp"
|
#include "../../PyPluginTrampoline.hpp"
|
||||||
|
|
||||||
#include "IPrinterAgent.hpp"
|
#include "IPrinterAgent.hpp"
|
||||||
|
#include "pybind11/pybind11.h"
|
||||||
|
#include <slic3r/plugin/PluginAuditManager.hpp>
|
||||||
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
class PyPrinterAgentPluginCapabilityTrampoline : public PyPluginCommonTrampoline<PrinterAgentPluginCapability>
|
class PyPrinterAgentPluginCapabilityTrampoline : public PyPluginCommonTrampoline<PrinterAgentPluginCapability>
|
||||||
@@ -96,6 +99,19 @@ public:
|
|||||||
get_filament_sync_mode);
|
get_filament_sync_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CameraStreamMode get_camera_stream_mode() const override
|
||||||
|
{
|
||||||
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, CameraStreamMode, PrinterAgentPluginCapability,
|
||||||
|
get_camera_stream_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_camera_url() const override
|
||||||
|
{
|
||||||
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, std::string, PrinterAgentPluginCapability, get_camera_url);
|
||||||
|
}
|
||||||
|
|
||||||
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) override
|
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
|
|||||||
Reference in New Issue
Block a user