You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3310 lines
147 KiB

// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2023 Intel Corporation. All Rights Reserved.
#include <librealsense2/rs_advanced_mode.hpp>
#include <librealsense2/rs.hpp>
#include <rs-config.h>
#include <third-party/filesystem/glob.h>
#include <imgui.h>
#include <imgui_internal.h>
#include "imgui-fonts-karla.hpp"
#include "imgui-fonts-fontawesome.hpp"
#include "imgui-fonts-monofont.hpp"
#include <rsutils/os/special-folder.h>
#include "os.h"
#include <rsutils/os/os.h>
#include "viewer.h"
#include "on-chip-calib.h"
#include "subdevice-model.h"
#include "device-model.h"
using namespace rs400;
using rsutils::json;
using namespace rs2::sw_update;
namespace rs2
{
void imgui_easy_theming(ImFont*& font_dynamic, ImFont*& font_18, ImFont*& monofont, int& font_size)
{
ImGuiStyle& style = ImGui::GetStyle();
ImGuiIO& io = ImGui::GetIO();
io.IniFilename = nullptr;
const int OVERSAMPLE = config_file::instance().get(configurations::performance::font_oversample);
font_size = config_file::instance().get( configurations::window::font_size );
static const ImWchar icons_ranges[] = { 0xf000, 0xf999, 0 }; // will not be copied by AddFont* so keep in scope.
{
ImFontConfig config_words;
config_words.OversampleV = OVERSAMPLE;
config_words.OversampleH = OVERSAMPLE;
font_dynamic = io.Fonts->AddFontFromMemoryCompressedTTF( karla_regular_compressed_data,
karla_regular_compressed_size,
(float)font_size );
ImFontConfig config_glyphs;
config_glyphs.MergeMode = true;
config_glyphs.OversampleV = OVERSAMPLE;
config_glyphs.OversampleH = OVERSAMPLE;
font_dynamic = io.Fonts->AddFontFromMemoryCompressedTTF(font_awesome_compressed_data,
font_awesome_compressed_size, 14.f, &config_glyphs, icons_ranges);
}
// Load 18px size fonts
{
ImFontConfig config_words;
config_words.OversampleV = OVERSAMPLE;
config_words.OversampleH = OVERSAMPLE;
font_18 = io.Fonts->AddFontFromMemoryCompressedTTF(karla_regular_compressed_data, karla_regular_compressed_size, 21.f, &config_words);
ImFontConfig config_glyphs;
config_glyphs.MergeMode = true;
config_glyphs.OversampleV = OVERSAMPLE;
config_glyphs.OversampleH = OVERSAMPLE;
font_18 = io.Fonts->AddFontFromMemoryCompressedTTF(font_awesome_compressed_data,
font_awesome_compressed_size, 20.f, &config_glyphs, icons_ranges);
}
// Load monofont
{
ImFontConfig config_words;
config_words.OversampleV = OVERSAMPLE;
config_words.OversampleH = OVERSAMPLE;
monofont = io.Fonts->AddFontFromMemoryCompressedTTF(monospace_compressed_data, monospace_compressed_size, 15.f);
ImFontConfig config_glyphs;
config_glyphs.MergeMode = true;
config_glyphs.OversampleV = OVERSAMPLE;
config_glyphs.OversampleH = OVERSAMPLE;
monofont = io.Fonts->AddFontFromMemoryCompressedTTF(font_awesome_compressed_data,
font_awesome_compressed_size, 14.f, &config_glyphs, icons_ranges);
}
style.WindowRounding = 0.0f;
style.ScrollbarRounding = 0.0f;
style.Colors[ImGuiCol_WindowBg] = dark_window_background;
style.Colors[ImGuiCol_Border] = black;
style.Colors[ImGuiCol_BorderShadow] = transparent;
style.Colors[ImGuiCol_FrameBg] = dark_window_background;
style.Colors[ImGuiCol_ScrollbarBg] = scrollbar_bg;
style.Colors[ImGuiCol_ScrollbarGrab] = scrollbar_grab;
style.Colors[ImGuiCol_ScrollbarGrabHovered] = scrollbar_grab + 0.1f;
style.Colors[ImGuiCol_ScrollbarGrabActive] = scrollbar_grab + (-0.1f);
style.Colors[ImGuiCol_ComboBg] = dark_window_background;
style.Colors[ImGuiCol_CheckMark] = regular_blue;
style.Colors[ImGuiCol_SliderGrab] = regular_blue;
style.Colors[ImGuiCol_SliderGrabActive] = regular_blue;
style.Colors[ImGuiCol_Button] = button_color;
style.Colors[ImGuiCol_ButtonHovered] = button_color + 0.1f;
style.Colors[ImGuiCol_ButtonActive] = button_color + (-0.1f);
style.Colors[ImGuiCol_Header] = header_color;
style.Colors[ImGuiCol_HeaderActive] = header_color + (-0.1f);
style.Colors[ImGuiCol_HeaderHovered] = header_color + 0.1f;
style.Colors[ImGuiCol_TitleBg] = title_color;
style.Colors[ImGuiCol_TitleBgCollapsed] = title_color;
style.Colors[ImGuiCol_TitleBgActive] = header_color;
}
void open_issue(std::string body)
{
std::string link = "https://github.com/IntelRealSense/librealsense/issues/new?body=" + url_encode(body);
open_url(link.c_str());
}
void open_issue(const device_models_list& devices)
{
std::stringstream ss;
rs2_error* e = nullptr;
ss << "| | |\n";
ss << "|---|---|\n";
ss << "|**librealsense**|" << api_version_to_string(rs2_get_api_version(&e)) << (is_debug() ? " DEBUG" : " RELEASE") << "|\n";
ss << "|**OS**|" << rsutils::os::get_os_name() << "|\n";
for (auto& dm : devices)
{
for (auto& kvp : dm->infos)
{
if (kvp.first != "Recommended Firmware Version" &&
kvp.first != "Debug Op Code" &&
kvp.first != "Physical Port" &&
kvp.first != "Product Id")
ss << "|**" << kvp.first << "**|" << kvp.second << "|\n";
}
}
ss << "\nPlease provide a description of the problem";
open_issue(ss.str());
}
template <typename T>
std::string safe_call(T t)
{
try
{
t();
return "";
}
catch (const error& e)
{
return error_to_string(e);
}
catch (const std::exception& e)
{
return e.what();
}
catch (...)
{
return "Unknown error occurred";
}
}
std::pair<std::string, std::string> get_device_name(const device& dev)
{
// retrieve device name
std::string name = (dev.supports(RS2_CAMERA_INFO_NAME)) ? dev.get_info(RS2_CAMERA_INFO_NAME) : "Unknown";
// retrieve device serial number
std::string serial = (dev.supports(RS2_CAMERA_INFO_SERIAL_NUMBER)) ? dev.get_info(RS2_CAMERA_INFO_SERIAL_NUMBER) : "Unknown";
std::stringstream s;
if (dev.is<playback>())
{
auto playback_dev = dev.as<playback>();
s << "Playback device: ";
name += (rsutils::string::from() << " (File: " << playback_dev.file_name() << ")");
}
s << std::setw(25) << std::left << name;
return std::make_pair(s.str(), serial); // push name and sn to list
}
void device_model::reset()
{
syncer->remove_syncer(dev_syncer);
subdevices.resize(0);
_recorder.reset();
}
device_model::~device_model()
{
for (auto&& n : related_notifications) n->dismiss(false);
_updates->set_device_status(*_updates_profile, false);
}
bool device_model::check_for_bundled_fw_update(const rs2::context &ctx, std::shared_ptr<notifications_model> not_model , bool reset_delay )
{
// LibRS can have a "bundled" FW binary downloaded during CMake. That's the version
// "available" to us, but it may not be there (e.g., no internet connection to download
// it). Lacking an available version, we try to let the user choose a "recommended"
// version for download. The recommended version is defined by the device (and comes
// from a #define).
// 'notification_type_is_displayed()' is used to detect if fw_update notification is on to avoid displaying it during FW update process when
// the device enters recovery mode
if( ! not_model->notification_type_is_displayed< fw_update_notification_model >()
&& ( dev.is< updatable >() || dev.is< update_device >() ) )
{
std::string fw;
std::string recommended_fw_ver;
int product_line = 0;
// Override with device info if info is available
if (dev.is<updatable>())
{
fw = dev.supports( RS2_CAMERA_INFO_FIRMWARE_VERSION )
? dev.get_info( RS2_CAMERA_INFO_FIRMWARE_VERSION )
: "";
recommended_fw_ver = dev.supports(RS2_CAMERA_INFO_RECOMMENDED_FIRMWARE_VERSION)
? dev.get_info(RS2_CAMERA_INFO_RECOMMENDED_FIRMWARE_VERSION)
: "";
}
product_line = dev.supports(RS2_CAMERA_INFO_PRODUCT_LINE)
? parse_product_line(dev.get_info(RS2_CAMERA_INFO_PRODUCT_LINE))
: -1; // invalid product line, will be handled later on
bool allow_rc_firmware = config_file::instance().get_or_default(
configurations::update::allow_rc_firmware,
false );
bool is_rc = ( product_line == RS2_PRODUCT_LINE_D400 ) && allow_rc_firmware;
std::string pid = dev.get_info(RS2_CAMERA_INFO_PRODUCT_ID);
std::string available_fw_ver = get_available_firmware_version( product_line, pid);
std::shared_ptr< firmware_update_manager > manager = nullptr;
if( dev.is<update_device>() || is_upgradeable( fw, available_fw_ver) )
{
recommended_fw_ver = available_fw_ver;
auto image = get_default_fw_image(product_line, pid);
if (image.empty())
{
not_model->add_log("could not detect a bundled FW version for the connected device", RS2_LOG_SEVERITY_WARN);
return false;
}
manager = std::make_shared< firmware_update_manager >( not_model,
*this,
dev,
ctx,
image,
true );
}
auto dev_name = get_device_name(dev);
if( dev.is<update_device>() || is_upgradeable( fw, recommended_fw_ver) )
{
std::stringstream msg;
if (dev.is<update_device>())
{
msg << dev_name.first << "\n(S/N " << dev.get_info(RS2_CAMERA_INFO_FIRMWARE_UPDATE_ID) << ")\n";
}
else
{
msg << dev_name.first << " (S/N " << dev_name.second << ")\n"
<< "Current Version: " << fw << "\n";
}
if (is_rc)
msg << "Release Candidate: " << recommended_fw_ver << " Pre-Release";
else
msg << "Recommended Version: " << recommended_fw_ver;
auto n = std::make_shared< fw_update_notification_model >( msg.str(),
manager,
false );
// The FW update delay ID include the dismissed recommended version and the device serial number
// This way a newer FW recommended version will not be dismissed
n->delay_id = "fw_update_alert." + recommended_fw_ver + "." + dev_name.second;
n->enable_complex_dismiss = true;
// If a delay request received in the past, reset it.
if( reset_delay ) n->reset_delay();
if( ! n->is_delayed() )
{
not_model->add_notification( n );
related_notifications.push_back( n );
return true;
}
}
else
{
if( ! fw.empty() && ! recommended_fw_ver.empty() )
{
std::stringstream msg;
msg << "Current FW >= Bundled FW for: " << dev_name.first << " (S/N " << dev_name.second << ")\n"
<< "Current Version: " << fw << "\n"
<< "Recommended Version: " << recommended_fw_ver;
not_model->add_log(msg.str(), RS2_LOG_SEVERITY_DEBUG);
}
}
}
return false;
}
void device_model::refresh_notifications(viewer_model& viewer)
{
for (auto&& n : related_notifications) n->dismiss(false);
auto name = get_device_name(dev);
// Inhibit on DQT / Playback device
if( _allow_remove && ( ! dev.is< playback >() ) )
check_for_device_updates(viewer);
if ((bool)config_file::instance().get(configurations::update::recommend_calibration))
{
for (auto&& model : subdevices)
{
if (model->supports_on_chip_calib())
{
// Make sure we don't spam calibration remainders too often:
time_t rawtime;
time(&rawtime);
std::string id = rsutils::string::from() << configurations::viewer::last_calib_notice << "." << name.second;
long long last_time = config_file::instance().get_or_default(id.c_str(), (long long)0);
std::string msg = rsutils::string::from()
<< name.first << " (S/N " << name.second << ")";
auto manager = std::make_shared<on_chip_calib_manager>(viewer, model, *this, dev);
auto n = std::make_shared<autocalib_notification_model>(
msg, manager, false);
// Recommend calibration once a week per device
if (rawtime - last_time < 60)
{
n->snoozed = true;
}
// NOTE: For now do not pre-emptively suggest auto-calibration
// TODO: Revert in later release
//viewer.not_model->add_notification(n);
//related_notifications.push_back(n);
}
}
}
}
device_model::device_model(device& dev, std::string& error_message, viewer_model& viewer, bool new_device_connected, bool remove)
: dev(dev),
_calib_model(dev, viewer.not_model),
syncer(viewer.syncer),
_update_readonly_options_timer(std::chrono::seconds(6)),
_detected_objects(std::make_shared< atomic_objects_in_frame >()),
_updates(viewer.updates),
_updates_profile(std::make_shared<dev_updates_profile::update_profile>()),
_allow_remove(remove)
{
auto name = get_device_name(dev);
id = rsutils::string::from() << name.first << ", " << name.second;
for (auto&& sub : dev.query_sensors())
{
auto s = std::make_shared<sensor>(sub);
auto objects = std::make_shared< atomic_objects_in_frame >();
// checking if the sensor is color_sensor or is D405 (with integrated RGB in depth sensor)
if (s->is<color_sensor>() || (dev.supports(RS2_CAMERA_INFO_PRODUCT_ID) && !strcmp(dev.get_info(RS2_CAMERA_INFO_PRODUCT_ID), "0B5B")))
objects = _detected_objects;
auto model = std::make_shared<subdevice_model>(dev, std::make_shared<sensor>(sub), objects, error_message, viewer, new_device_connected);
subdevices.push_back(model);
}
// Initialize static camera info:
for (auto i = 0; i < RS2_CAMERA_INFO_COUNT; i++)
{
auto info = static_cast<rs2_camera_info>(i);
try
{
if (dev.supports(info))
{
auto value = dev.get_info(info);
infos.push_back({ std::string(rs2_camera_info_to_string(info)),
std::string(value) });
}
}
catch (...)
{
infos.push_back({ std::string(rs2_camera_info_to_string(info)),
std::string("???") });
}
}
if (dev.is<playback>())
{
for (auto&& sub : subdevices)
{
for (auto&& p : sub->profiles)
{
sub->stream_enabled[p.unique_id()] = true;
}
}
play_defaults(viewer);
}
refresh_notifications(viewer);
auto path = rsutils::os::get_special_folder( rsutils::os::special_folder::user_documents );
path += "librealsense2/presets/";
try
{
std::string name = dev.get_info(RS2_CAMERA_INFO_NAME);
std::smatch match;
if( ! std::regex_search( name, match, std::regex( "^Intel RealSense (\\S+)" ) ) )
throw std::runtime_error( "cannot parse device name from '" + name + "'" );
glob(
path,
std::string( match[1] ) + " *.preset",
[&]( std::string const & file ) {
advanced_mode_settings_file_names.insert( path + file );
},
false ); // recursive
}
catch( const std::exception & e )
{
LOG_WARNING( "Exception caught trying to detect presets: " << e.what() );
}
}
void device_model::play_defaults(viewer_model& viewer)
{
if (!dev_syncer)
dev_syncer = viewer.syncer->create_syncer();
for (auto&& sub : subdevices)
{
if (!sub->streaming)
{
std::vector<rs2::stream_profile> profiles;
try
{
profiles = sub->get_selected_profiles();
}
catch (...)
{
continue;
}
if (profiles.empty())
continue;
std::string friendly_name = sub->s->get_info(RS2_CAMERA_INFO_NAME);
if ((friendly_name.find("Tracking") != std::string::npos) ||
(friendly_name.find("Motion") != std::string::npos))
{
viewer.synchronization_enable_prev_state = viewer.synchronization_enable.load();
viewer.synchronization_enable = false;
}
sub->play(profiles, viewer, dev_syncer);
for (auto&& profile : profiles)
{
viewer.begin_stream(sub, profile);
}
}
}
}
void device_model::start_recording(const std::string& path, std::string& error_message)
{
if (_recorder != nullptr)
{
return; //already recording
}
try
{
int compression_mode = config_file::instance().get(configurations::record::compression_mode);
if (compression_mode == 2)
_recorder = std::make_shared<recorder>(path, dev);
else
_recorder = std::make_shared<recorder>(path, dev, compression_mode == 0);
for (auto&& sub_dev_model : subdevices)
{
sub_dev_model->_is_being_recorded = true;
}
is_recording = true;
}
catch (const rs2::error& e)
{
error_message = error_to_string(e);
}
catch (const std::exception& e)
{
error_message = e.what();
}
}
void device_model::stop_recording(viewer_model& viewer)
{
auto saved_to_filename = _recorder->filename();
_recorder.reset();
for (auto&& sub_dev_model : subdevices)
{
sub_dev_model->_is_being_recorded = false;
}
is_recording = false;
notification_data nd{ rsutils::string::from() << "Saved recording to:\n" << saved_to_filename,
RS2_LOG_SEVERITY_INFO,
RS2_NOTIFICATION_CATEGORY_UNKNOWN_ERROR };
viewer.not_model->add_notification(nd);
}
void device_model::pause_record()
{
_recorder->pause();
}
void device_model::resume_record()
{
_recorder->resume();
}
int device_model::draw_playback_controls(ux_window& window, ImFont* font, viewer_model& viewer)
{
auto p = dev.as<playback>();
rs2_playback_status current_playback_status = p.current_status();
const int playback_control_height = 35;
const float combo_box_width = 90.f;
const float icon_width = 28;
const float line_width = 255; //Ideally should use: ImGui::GetContentRegionMax().x
//Line looks like this ("-" == space, "[]" == icon, "[ ]" == combo_box): |-[]-[]-[]-[]-[]-[ ]-[]-|
const int num_icons_in_line = 6;
const int num_combo_boxes_in_line = 1;
const int num_spaces_in_line = num_icons_in_line + num_combo_boxes_in_line + 1;
const float required_row_width = (num_combo_boxes_in_line * combo_box_width) + (num_icons_in_line * icon_width);
float space_width = std::max(line_width - required_row_width, 0.f) / num_spaces_in_line;
ImVec2 button_dim = { icon_width, icon_width };
const bool supports_playback_step = current_playback_status == RS2_PLAYBACK_STATUS_PAUSED;
ImGui::PushFont(font);
//////////////////// Step Backwards Button ////////////////////
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + space_width);
std::string label = rsutils::string::from() << textual_icons::step_backward << "##Step Backward " << id;
if (ImGui::ButtonEx(label.c_str(), button_dim, supports_playback_step ? 0 : ImGuiButtonFlags_Disabled))
{
int fps = 0;
for (auto&& s : viewer.streams)
{
if (s.second.profile.fps() > fps)
fps = s.second.profile.fps();
}
auto curr_frame = p.get_position();
uint64_t step = fps ? uint64_t(1000.0 / (float)fps * 1e6) : 1000000ULL;
if (curr_frame >= step)
{
p.seek(std::chrono::nanoseconds(curr_frame - step));
}
}
if (ImGui::IsItemHovered())
{
std::string tooltip = rsutils::string::from() << "Step Backwards" << (supports_playback_step ? "" : "(Not available)");
ImGui::SetTooltip("%s", tooltip.c_str());
}
ImGui::SameLine();
//////////////////// Step Backwards Button ////////////////////
//////////////////// Stop Button ////////////////////
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + space_width);
label = rsutils::string::from() << textual_icons::stop << "##Stop Playback " << id;
if (ImGui::ButtonEx(label.c_str(), button_dim))
{
bool prev = _playback_repeat;
_playback_repeat = false;
p.stop();
_playback_repeat = prev;
}
if (ImGui::IsItemHovered())
{
std::string tooltip = rsutils::string::from() << "Stop Playback";
ImGui::SetTooltip("%s", tooltip.c_str());
}
ImGui::SameLine();
//////////////////// Stop Button ////////////////////
//////////////////// Pause/Play Button ////////////////////
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + space_width);
if (current_playback_status == RS2_PLAYBACK_STATUS_PAUSED || current_playback_status == RS2_PLAYBACK_STATUS_STOPPED)
{
label = rsutils::string::from() << textual_icons::play << "##Play " << id;
if (ImGui::ButtonEx(label.c_str(), button_dim))
{
if (current_playback_status == RS2_PLAYBACK_STATUS_STOPPED)
{
play_defaults(viewer);
}
else
{
syncer->on_frame = [] {};
for (auto&& s : subdevices)
{
s->on_frame = [] {};
if (s->streaming)
s->resume();
}
viewer.paused = false;
p.resume();
}
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip(current_playback_status == RS2_PLAYBACK_STATUS_PAUSED ? "Resume Playback" : "Start Playback");
}
}
else
{
label = rsutils::string::from() << textual_icons::pause << "##Pause Playback " << id;
if (ImGui::ButtonEx(label.c_str(), button_dim))
{
p.pause();
for (auto&& s : subdevices)
{
if (s->streaming)
s->pause();
}
viewer.paused = true;
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("Pause Playback");
}
}
ImGui::SameLine();
//////////////////// Pause/Play Button ////////////////////
//////////////////// Step Forward Button ////////////////////
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + space_width);
label = rsutils::string::from() << textual_icons::step_forward << "##Step Forward " << id;
if (ImGui::ButtonEx(label.c_str(), button_dim, supports_playback_step ? 0 : ImGuiButtonFlags_Disabled))
{
int fps = 0;
for (auto&& s : viewer.streams)
{
if (s.second.profile.fps() > fps)
fps = s.second.profile.fps();
}
auto curr_frame = p.get_position();
uint64_t step = fps ? uint64_t(1000.0 / (float)fps * 1e6) : 1000000ULL;
p.seek(std::chrono::nanoseconds(curr_frame + step));
}
if (ImGui::IsItemHovered())
{
std::string tooltip = rsutils::string::from() << "Step Forward" << (supports_playback_step ? "" : "(Not available)");
ImGui::SetTooltip("%s", tooltip.c_str());
}
ImGui::SameLine();
//////////////////// Step Forward Button ////////////////////
/////////////////// Repeat Button /////////////////////
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + space_width);
if (_playback_repeat)
{
ImGui::PushStyleColor(ImGuiCol_Text, light_blue);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, light_blue);
}
else
{
ImGui::PushStyleColor(ImGuiCol_Text, white);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, white);
}
label = rsutils::string::from() << textual_icons::repeat << "##Repeat " << id;
if (ImGui::ButtonEx(label.c_str(), button_dim))
{
_playback_repeat = !_playback_repeat;
}
if (ImGui::IsItemHovered())
{
std::string tooltip = rsutils::string::from() << (_playback_repeat ? "Disable " : "Enable ") << "Repeat ";
ImGui::SetTooltip("%s", tooltip.c_str());
}
ImGui::PopStyleColor(2);
ImGui::SameLine();
/////////////////// Repeat Button /////////////////////
//////////////////// Speed combo box ////////////////////
auto pos = ImGui::GetCursorPos();
const float speed_combo_box_v_alignment = 3.f;
ImGui::SetCursorPos({ pos.x + space_width, pos.y + speed_combo_box_v_alignment });
ImGui::PushItemWidth(combo_box_width);
ImGui::PushStyleColor(ImGuiCol_FrameBg, sensor_bg);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, sensor_bg);
label = rsutils::string::from() << "## " << id;
if (ImGui::Combo(label.c_str(), &playback_speed_index, "Speed: x0.25\0Speed: x0.5\0Speed: x1\0Speed: x1.5\0Speed: x2\0\0", -1, false))
{
float speed = 1;
switch (playback_speed_index)
{
case 0: speed = 0.25f; break;
case 1: speed = 0.5f; break;
case 2: speed = 1.0f; break;
case 3: speed = 1.5f; break;
case 4: speed = 2.0f; break;
default:
throw std::runtime_error( rsutils::string::from() << "Speed #" << playback_speed_index << " is unhandled");
}
p.set_playback_speed(speed);
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("Change playback speed rate");
}
ImGui::PopStyleColor(2);
ImGui::SameLine();
//Restore Y movement
pos = ImGui::GetCursorPos();
ImGui::SetCursorPos({ pos.x, pos.y - speed_combo_box_v_alignment });
//////////////////// Speed combo box ////////////////////
//////////////////// Info Icon ////////////////////
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + space_width);
draw_info_icon(window, font, button_dim);
//////////////////// Info Icon ////////////////////
ImGui::PopFont();
return playback_control_height;
}
std::string device_model::pretty_time(std::chrono::nanoseconds duration)
{
using namespace std::chrono;
auto hhh = duration_cast<hours>(duration);
duration -= hhh;
auto mm = duration_cast<minutes>(duration);
duration -= mm;
auto ss = duration_cast<seconds>(duration);
duration -= ss;
auto ms = duration_cast<milliseconds>(duration);
std::ostringstream stream;
stream << std::setfill('0') << std::setw(hhh.count() >= 10 ? 2 : 1) << hhh.count() << ':' <<
std::setfill('0') << std::setw(2) << mm.count() << ':' <<
std::setfill('0') << std::setw(2) << ss.count() << '.' <<
std::setfill('0') << std::setw(3) << ms.count();
return stream.str();
}
int device_model::draw_seek_bar()
{
auto pos = ImGui::GetCursorPos();
auto p = dev.as<playback>();
//rs2_playback_status current_playback_status = p.current_status();
int64_t playback_total_duration = p.get_duration().count();
auto progress = p.get_position();
double part = (1.0 * progress) / playback_total_duration;
seek_pos = static_cast<int>(std::max(0.0, std::min(part, 1.0)) * 100);
auto playback_status = p.current_status();
if (seek_pos != 0 && playback_status == RS2_PLAYBACK_STATUS_STOPPED)
{
seek_pos = 0;
}
float seek_bar_width = 300.f;
ImGui::PushItemWidth(seek_bar_width);
std::string label1 = "## " + id;
if (ImGui::SeekSlider(label1.c_str(), &seek_pos, ""))
{
//Seek was dragged
if (playback_status != RS2_PLAYBACK_STATUS_STOPPED) //Ignore seek when playback is stopped
{
auto duration_db = std::chrono::duration_cast<std::chrono::duration<double, std::nano>>(p.get_duration());
auto single_percent = duration_db.count() / 100;
auto seek_time = std::chrono::duration<double, std::nano>(seek_pos * single_percent);
p.seek(std::chrono::duration_cast<std::chrono::nanoseconds>(seek_time));
}
}
ImGui::SetCursorPos({ pos.x, pos.y + 17 });
std::string time_elapsed = pretty_time(std::chrono::nanoseconds(progress));
std::string duration_str = pretty_time(std::chrono::nanoseconds(playback_total_duration));
ImGui::Text("%s", time_elapsed.c_str());
ImGui::SameLine();
float pos_y = ImGui::GetCursorPosY();
ImGui::SetCursorPos({ pos.x + seek_bar_width - 45 , pos_y });
ImGui::Text("%s", duration_str.c_str());
return 50;
}
int device_model::draw_playback_panel(ux_window& window, ImFont* font, viewer_model& view)
{
ImGui::PushStyleColor(ImGuiCol_Button, sensor_bg);
ImGui::PushStyleColor(ImGuiCol_Text, light_grey);
ImGui::PushStyleColor(ImGuiCol_PopupBg, almost_white_bg);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, from_rgba(0, 0xae, 0xff, 255));
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, white);
auto pos = ImGui::GetCursorPos();
auto controls_height = draw_playback_controls(window, font, view);
float seek_bar_left_alignment = 4.f;
ImGui::SetCursorPos({ pos.x + seek_bar_left_alignment, pos.y + controls_height });
ImGui::PushFont(font);
auto seek_bar_height = draw_seek_bar();
ImGui::PopFont();
ImGui::PopStyleColor(5);
return controls_height + seek_bar_height;
}
bool device_model::prompt_toggle_advanced_mode(bool enable_advanced_mode, const std::string& message_text, std::vector<std::string>& restarting_device_info, viewer_model& view, ux_window& window, const std::string& error_message)
{
bool keep_showing = true;
bool yes_was_chosen = false;
if (yes_no_dialog("Advanced Mode", message_text, yes_was_chosen, window, error_message))
{
if (yes_was_chosen)
{
dev.as<advanced_mode>().toggle_advanced_mode(enable_advanced_mode);
restarting_device_info = get_device_info(dev, false);
view.not_model->add_log(enable_advanced_mode ? "Turning on advanced mode..." : "Turning off advanced mode...");
}
keep_showing = false;
}
return keep_showing;
}
bool device_model::draw_advanced_controls(viewer_model& view, ux_window& window, std::string& error_message)
{
bool was_set = false;
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, { 0.9f, 0.9f, 0.9f, 1 });
auto is_advanced_mode = dev.is<advanced_mode>();
if (is_advanced_mode && ImGui::TreeNode("Advanced Controls"))
{
try
{
auto advanced = dev.as<advanced_mode>();
if (advanced.is_enabled())
{
draw_advanced_mode_controls(advanced, amc, get_curr_advanced_controls, was_set, error_message);
}
else
{
ImGui::TextColored(redish, "Device is not in advanced mode");
std::string button_text = rsutils::string::from() << "Turn on Advanced Mode" << "##" << id;
static bool show_yes_no_modal = false;
if (ImGui::Button(button_text.c_str(), ImVec2{ 226, 0 }))
{
show_yes_no_modal = true;
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("Advanced mode is a persistent camera state unlocking calibration formats and depth generation controls\nYou can always reset the camera to factory defaults by disabling advanced mode");
}
if (show_yes_no_modal)
{
show_yes_no_modal = prompt_toggle_advanced_mode(true, "\t\tAre you sure you want to turn on Advanced Mode?\t\t", restarting_device_info, view, window, error_message);
}
}
}
catch (const std::exception& ex)
{
error_message = ex.what();
}
ImGui::TreePop();
}
ImGui::PopStyleColor();
return was_set;
}
void device_model::draw_info_icon(ux_window& window, ImFont* font, const ImVec2& size)
{
std::string info_button_name = rsutils::string::from() << textual_icons::info_circle << "##" << id;
auto info_button_color = show_device_info ? light_blue : light_grey;
ImGui::PushStyleColor(ImGuiCol_Text, info_button_color);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, info_button_color);
if (ImGui::Button(info_button_name.c_str(), size))
{
show_device_info = !show_device_info;
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("%s", show_device_info ? "Hide Device Details" : "Show Device Details");
window.link_hovered();
}
ImGui::PopStyleColor(2);
}
void device_model::begin_update_unsigned(viewer_model& viewer, std::string& error_message)
{
try
{
std::vector<uint8_t> data;
auto ret = file_dialog_open(open_file, "Unsigned Firmware Image\0*.bin\0*.img\0", NULL, NULL);
if (ret)
{
std::ifstream file(ret, std::ios::binary | std::ios::in);
if (file.good())
{
data = std::vector<uint8_t>((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
}
else
{
error_message = rsutils::string::from() << "Could not open file '" << ret << "'";
return;
}
}
else return; // Aborted by the user
auto manager = std::make_shared<firmware_update_manager>(viewer.not_model, *this, dev, viewer.ctx, data, false);
auto n = std::make_shared<fw_update_notification_model>(
"Manual Update requested", manager, true);
viewer.not_model->add_notification(n);
for (auto&& n : related_notifications)
if (dynamic_cast<fw_update_notification_model*>(n.get()))
n->dismiss(false);
auto invoke = [n](std::function<void()> action) {
n->invoke(action);
};
manager->start(invoke);
}
catch (const error& e)
{
error_message = error_to_string(e);
}
catch (const std::exception& e)
{
error_message = e.what();
}
}
void device_model::begin_update(std::vector<uint8_t> data, viewer_model& viewer, std::string& error_message)
{
try
{
if (data.size() == 0)
{
auto ret = file_dialog_open(open_file, "Signed Firmware Image\0*.bin\0*.img\0", NULL, NULL);
if (ret)
{
std::ifstream file(ret, std::ios::binary | std::ios::in);
if (file.good())
{
data = std::vector<uint8_t>((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
}
else
{
error_message = rsutils::string::from() << "Could not open file '" << ret << "'";
return;
}
}
else return; // Aborted by the user
}
auto manager = std::make_shared<firmware_update_manager>(viewer.not_model, *this, dev, viewer.ctx, data, true);
auto n = std::make_shared<fw_update_notification_model>(
"Manual Update requested", manager, true);
viewer.not_model->add_notification(n);
for (auto&& n : related_notifications)
n->dismiss(false);
auto invoke = [n](std::function<void()> action) {
n->invoke(action);
};
manager->start(invoke);
}
catch (const error& e)
{
error_message = error_to_string(e);
}
catch (const std::exception& e)
{
error_message = e.what();
}
}
void device_model::check_for_device_updates(viewer_model& viewer, bool activated_by_user )
{
std::weak_ptr< updates_model > updates_model_protected( viewer.updates );
std::weak_ptr< dev_updates_profile::update_profile > update_profile_protected(
_updates_profile );
std::weak_ptr< notifications_model > notification_model_protected( viewer.not_model );
const context & ctx( viewer.ctx );
std::thread check_for_device_updates_thread( [ctx,
updates_model_protected,
notification_model_protected,
this,
update_profile_protected,
activated_by_user]() {
try
{
bool need_to_check_bundle = true;
std::string server_url
= config_file::instance().get( configurations::update::sw_updates_url );
bool use_local_file = false;
const std::string local_file_prefix = "file://";
// If URL contain a "file://" prefix, we open it as local file and not downloading
// it from a server
if( server_url.find( local_file_prefix ) == 0 )
{
use_local_file = true;
server_url.erase( 0, local_file_prefix.length() );
}
sw_update::dev_updates_profile updates_profile( dev, server_url, use_local_file );
bool fail_access_db = false;
bool sw_online_update_available = updates_profile.retrieve_updates( sw_update::LIBREALSENSE, fail_access_db);
bool fw_online_update_available = updates_profile.retrieve_updates( sw_update::FIRMWARE, fail_access_db);
bool fw_bundled_update_available = false;
if (sw_online_update_available || fw_online_update_available)
{
if (auto update_profile = update_profile_protected.lock())
{
*update_profile = updates_profile.get_update_profile();
updates_model::update_profile_model updates_profile_model(*update_profile,
ctx,
this);
dev_updates_profile::version_info sw_update_info, fw_update_info;
bool essential_sw_update_found = update_profile->get_sw_update(sw_update::ESSENTIAL, sw_update_info);
bool essential_fw_update_found = update_profile->get_fw_update(sw_update::ESSENTIAL, fw_update_info);
if (essential_sw_update_found || essential_fw_update_found)
{
if (auto viewer_updates = updates_model_protected.lock())
{
viewer_updates->add_profile(updates_profile_model);
need_to_check_bundle = false;
// Log the essential updates
if (auto nm = notification_model_protected.lock())
{
if( essential_sw_update_found )
nm->add_log(
rsutils::string::from()
<< update_profile->device_name << " (S/N "
<< update_profile->serial_number << ")\n"
<< "Current SW version: " << std::string( update_profile->software_version )
<< "\nEssential SW version: "
<< std::string( sw_update_info.ver ),
RS2_LOG_SEVERITY_WARN );
if( essential_fw_update_found )
nm->add_log(
rsutils::string::from()
<< update_profile->device_name << " (S/N "
<< update_profile->serial_number << ")\n"
<< "Current FW version: " << std::string( update_profile->firmware_version )
<< "\nEssential FW version: "
<< std::string( fw_update_info.ver ),
RS2_LOG_SEVERITY_WARN );
}
}
}
else
{
if (auto viewer_updates = updates_model_protected.lock())
{
// Do not create pop ups if the viewer updates windows is on
if (viewer_updates->has_updates())
{
need_to_check_bundle = false;
}
else
{
if (sw_online_update_available)
{
if (auto nm = notification_model_protected.lock())
{
handle_online_sw_update( nm, update_profile, activated_by_user);
}
}
if (fw_online_update_available)
{
if (auto nm = notification_model_protected.lock())
{
need_to_check_bundle = !handle_online_fw_update( ctx, nm, update_profile , activated_by_user);
}
}
}
}
// For updating current device profile if exists (Could update firmware version)
if (auto viewer_updates = updates_model_protected.lock())
{
viewer_updates->update_profile(updates_profile_model);
}
}
}
}
else if( auto nm = notification_model_protected.lock() )
{
if ( activated_by_user && fail_access_db )
nm->add_notification( { rsutils::string::from() << textual_icons::wifi << " Unable to retrieve updates.\nPlease check your network connection.\n",
RS2_LOG_SEVERITY_ERROR,
RS2_NOTIFICATION_CATEGORY_UNKNOWN_ERROR } );
else
nm->add_log( "No online SW / FW updates available" );
}
// If no on-line updates notification, offer bundled FW update if needed
if( need_to_check_bundle
&& (bool)config_file::instance().get( configurations::update::recommend_updates ) )
{
if( auto nm = notification_model_protected.lock() )
{
fw_bundled_update_available = check_for_bundled_fw_update( ctx, nm , activated_by_user);
}
}
// When no updates available (on-line + bundled), add a notification to indicate "all up to date"
if( activated_by_user && ! fail_access_db && ! sw_online_update_available
&& ! fw_online_update_available && ! fw_bundled_update_available )
{
auto n = std::make_shared< sw_update_up_to_date_model >();
auto name = get_device_name(dev);
n->delay_id = "no_updates_alert." + name.second;
if (auto nm = notification_model_protected.lock())
{
nm->add_notification(n);
related_notifications.push_back(n);
}
}
}
catch( const std::exception & e )
{
auto error = e.what();
}
} );
check_for_device_updates_thread.detach();
}
float device_model::draw_device_panel(float panel_width,
ux_window& window,
std::string& error_message,
viewer_model& viewer)
{
/*
=============================
[o] [@] [i] [=]
Record Sync Info More
=============================
*/
const bool is_playback_device = dev.is<playback>();
const float device_panel_height = 60.0f;
auto panel_pos = ImGui::GetCursorPos();
ImGui::PushFont(window.get_large_font());
ImGui::PushStyleColor(ImGuiCol_Button, sensor_bg);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, sensor_bg);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, sensor_bg);
ImGui::PushStyleColor(ImGuiCol_Text, light_grey);
ImGui::PushStyleColor(ImGuiCol_PopupBg, almost_white_bg);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, light_blue);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, light_grey);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(5, 5));
////////////////////////////////////////
// Draw recording icon
////////////////////////////////////////
bool is_streaming = std::any_of(subdevices.begin(), subdevices.end(), [](const std::shared_ptr<subdevice_model>& sm)
{
return sm->streaming;
});
textual_icon button_icon = is_recording ? textual_icons::stop : textual_icons::circle;
const float icons_width = 78.0f;
const ImVec2 device_panel_icons_size{ icons_width, 25 };
std::string record_button_name = rsutils::string::from() << button_icon << "##" << id;
auto record_button_color = is_recording ? light_blue : light_grey;
ImGui::PushStyleColor(ImGuiCol_Text, record_button_color);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, record_button_color);
if (ImGui::ButtonEx(record_button_name.c_str(), device_panel_icons_size, (disable_record_button_logic(is_streaming, is_playback_device)) ? ImGuiButtonFlags_Disabled : 0))
{
if (is_recording) //is_recording is changed inside stop/start_recording
{
stop_recording(viewer);
}
else
{
int recording_setting = config_file::instance().get(configurations::record::file_save_mode);
std::string path = "";
std::string default_path = config_file::instance().get(configurations::record::default_path);
if (!ends_with(default_path, "/") && !ends_with(default_path, "\\")) default_path += "/";
std::string default_filename = rs2::get_timestamped_file_name() + ".bag";
if (recording_setting == 0 && default_path.size() > 1 )
{
path = default_path + default_filename;
}
else
{
if (const char* ret = file_dialog_open(file_dialog_mode::save_file, "ROS-bag\0*.bag\0",
default_path.c_str(), default_filename.c_str()))
{
path = ret;
if (!ends_with(rsutils::string::to_lower(path), ".bag")) path += ".bag";
}
}
if (path != "") start_recording(path, error_message);
}
}
if (ImGui::IsItemHovered())
{
std::string record_button_hover_text = get_record_button_hover_text(is_streaming);
ImGui::SetTooltip("%s", record_button_hover_text.c_str());
if (is_streaming) window.link_hovered();
}
ImGui::PopStyleColor(2);
ImGui::SameLine();
////////////////////////////////////////
// Draw Sync icon
////////////////////////////////////////
std::string sync_button_name = rsutils::string::from() << textual_icons::refresh << "##" << id;
bool is_sync_enabled = false; //TODO: use device's member
auto sync_button_color = is_sync_enabled ? light_blue : light_grey;
ImGui::PushStyleColor(ImGuiCol_Text, sync_button_color);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, sync_button_color);
if (ImGui::ButtonEx(sync_button_name.c_str(), device_panel_icons_size, ImGuiButtonFlags_Disabled))
{
is_sync_enabled = !is_sync_enabled;
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("%s", is_sync_enabled ? "Disable streams synchronization" : "Enable streams synchronization");
}
ImGui::PopStyleColor(2);
ImGui::SameLine();
////////////////////////////////////////
// Draw Info icon
////////////////////////////////////////
draw_info_icon(window, window.get_font(), device_panel_icons_size);
ImGui::SameLine();
////////////////////////////////////////
// Draw Menu icon
////////////////////////////////////////
std::string label = rsutils::string::from() << "device_menu" << id;
std::string bars_button_name = rsutils::string::from() << textual_icons::bars << "##" << id;
if (ImGui::Button(bars_button_name.c_str(), device_panel_icons_size))
{
ImGui::OpenPopup(label.c_str());
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("%s", "Click for more");
window.link_hovered();
}
ImGui::PopFont();
ImGui::PushFont(window.get_font());
static bool keep_showing_advanced_mode_modal = false;
bool open_calibration_ui = false;
if (ImGui::BeginPopup(label.c_str()))
{
bool something_to_show = false;
ImGui::PushStyleColor(ImGuiCol_Text, dark_grey);
if (_allow_remove)
{
something_to_show = true;
if (auto adv = dev.as<advanced_mode>())
{
const bool is_advanced_mode_enabled = adv.is_enabled();
bool selected = is_advanced_mode_enabled;
if (ImGui::MenuItem("Advanced Mode", nullptr, &selected))
{
keep_showing_advanced_mode_modal = true;
}
ImGui::Separator();
}
if (ImGui::Selectable("Hardware Reset"))
{
try
{
restarting_device_info = get_device_info(dev, false);
dev.hardware_reset();
}
catch (const error& e)
{
error_message = error_to_string(e);
}
catch (const std::exception& e)
{
error_message = e.what();
}
}
// fw update disabled when any sensor is streaming
ImGuiSelectableFlags updateFwFlags = (is_streaming) ? ImGuiSelectableFlags_Disabled : 0;
if (dev.is<rs2::updatable>() || dev.is<rs2::update_device>())
{
if (ImGui::Selectable("Update Firmware...", false, updateFwFlags))
{
begin_update({}, viewer, error_message);
}
if (ImGui::IsItemHovered())
{
std::string tooltip = rsutils::string::from()
<< "Install official signed firmware from file to the device"
<< ( is_streaming ? " (Disabled while streaming)" : "" );
ImGui::SetTooltip("%s", tooltip.c_str());
}
if( dev.supports( RS2_CAMERA_INFO_PRODUCT_LINE )
&& ( dev.get_info( RS2_CAMERA_INFO_PRODUCT_LINE ) ) )
{
if( ImGui::Selectable( "Check For Updates", false, updateFwFlags ) )
{
// Remove all previous SW/FW update notifications before triggering checking for updates logic
for( auto && n : related_notifications )
{
if( n->is< fw_update_notification_model >()
|| n->is< sw_recommended_update_alert_model >() )
n->dismiss( false ); // No need for snooze, if needed a new notification will be popped
}
check_for_device_updates( viewer , true);
}
}
if (ImGui::IsItemHovered())
{
std::string tooltip = rsutils::string::from() << "Check for SW / FW updates";
ImGui::SetTooltip("%s", tooltip.c_str());
}
}
bool is_locked = true;
if (dev.supports(RS2_CAMERA_INFO_CAMERA_LOCKED))
is_locked = std::string(dev.get_info(RS2_CAMERA_INFO_CAMERA_LOCKED)) == "YES" ? true : false;
if (dev.is<rs2::updatable>() && !is_locked)
{
auto pl = dev.get_info(RS2_CAMERA_INFO_PRODUCT_LINE);
bool is_d400_device = (std::string(pl) == "D400");
if( is_d400_device )
{
if (ImGui::Selectable("Update Unsigned Firmware...", false, updateFwFlags))
{
begin_update_unsigned(viewer, error_message);
}
if (ImGui::IsItemHovered())
{
std::string tooltip = rsutils::string::from()
<< "Install non official unsigned firmware from file to the device"
<< ( is_streaming ? " (Disabled while streaming)" : "" );
ImGui::SetTooltip("%s", tooltip.c_str());
}
}
}
}
bool has_autocalib = false;
for (auto&& sub : subdevices)
{
if (sub->supports_on_chip_calib() && !has_autocalib)
{
something_to_show = true;
std::string device_pid = sub->s->supports(RS2_CAMERA_INFO_PRODUCT_ID) ? sub->s->get_info(RS2_CAMERA_INFO_PRODUCT_ID) : "unknown";
std::string device_usb_type = sub->s->supports(RS2_CAMERA_INFO_USB_TYPE_DESCRIPTOR) ? sub->s->get_info(RS2_CAMERA_INFO_USB_TYPE_DESCRIPTOR) : "unknown";
bool show_disclaimer = val_in_range(device_pid, { std::string("0AD2"), std::string("0AD3") }); // Specific for D410/5
bool disable_fl_cal = (((device_pid == "0B5C") || show_disclaimer) &&
(!starts_with(device_usb_type, "3."))); // D410/D15/D455@USB2
if (ImGui::Selectable("On-Chip Calibration"))
{
try
{
if (show_disclaimer)
{
auto disclaimer_notice = std::make_shared<ucal_disclaimer_model>();
viewer.not_model->add_notification(disclaimer_notice);
}
auto manager = std::make_shared<on_chip_calib_manager>(viewer, sub, *this, dev);
auto n = std::make_shared<autocalib_notification_model>("", manager, false);
viewer.not_model->add_notification(n);
n->forced = true;
n->update_state = autocalib_notification_model::RS2_CALIB_STATE_SELF_INPUT;
for (auto&& n : related_notifications)
if (dynamic_cast<autocalib_notification_model*>(n.get()))
n->dismiss(false);
related_notifications.push_back(n);
}
catch (const error& e)
{
error_message = error_to_string(e);
}
catch (const std::exception& e)
{
error_message = e.what();
}
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("This will improve the depth noise.\n"
"Point at a scene that normally would have > 50 %% valid depth pixels,\n"
"then press calibrate."
"The health-check will be calculated.\n"
"If >0.25 we recommend applying the new calibration.\n"
"\"White wall\" mode should only be used when pointing at a flat white wall with projector on");
if (ImGui::Selectable("Focal Length Calibration"))
{
try
{
if (disable_fl_cal)
{
auto disable_fl_notice = std::make_shared<fl_cal_limitation_model>();
viewer.not_model->add_notification(disable_fl_notice);
}
else
{
std::shared_ptr< subdevice_model> sub_color;
for (auto&& sub2 : subdevices)
{
if (sub2->s->is<rs2::color_sensor>())
{
sub_color = sub2;
break;
}
}
if (show_disclaimer)
{
auto disclaimer_notice = std::make_shared<ucal_disclaimer_model>();
viewer.not_model->add_notification(disclaimer_notice);
}
auto manager = std::make_shared<on_chip_calib_manager>(viewer, sub, *this, dev, sub_color);
auto n = std::make_shared<autocalib_notification_model>("", manager, false);
viewer.not_model->add_notification(n);
n->forced = true;
n->update_state = autocalib_notification_model::RS2_CALIB_STATE_FL_INPUT;
for (auto&& n : related_notifications)
if (dynamic_cast<autocalib_notification_model*>(n.get()))
n->dismiss(false);
related_notifications.push_back(n);
manager->start_fl_viewer();
}
}
catch (const error& e)
{
error_message = error_to_string(e);
}
catch (const std::exception& e)
{
error_message = e.what();
}
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Focal length calibration is used to adjust camera focal length with specific target.");
if (ImGui::Selectable("Tare Calibration"))
{
try
{
if (show_disclaimer)
{
auto disclaimer_notice = std::make_shared<ucal_disclaimer_model>();
viewer.not_model->add_notification(disclaimer_notice);
}
auto manager = std::make_shared<on_chip_calib_manager>(viewer, sub, *this, dev);
auto n = std::make_shared<autocalib_notification_model>("", manager, false);
viewer.not_model->add_notification(n);
n->forced = true;
n->update_state = autocalib_notification_model::RS2_CALIB_STATE_TARE_INPUT;
for (auto&& n : related_notifications)
if (dynamic_cast<autocalib_notification_model*>(n.get()))
n->dismiss(false);
related_notifications.push_back(n);
}
catch (const error& e)
{
error_message = error_to_string(e);
}
catch (const std::exception& e)
{
error_message = e.what();
}
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Tare calibration is used to adjust camera absolute distance to flat target.\n"
"User needs either to enter the known ground truth or use the get button\n"
"with specific target to get the ground truth.");
//#define UVMAP_CAL
#ifdef UVMAP_CAL // Disabled due to stability and maturity levels
try
{
for (auto&& sub2 : subdevices)
{
if (sub2->s->is<rs2::color_sensor>())
{
if (ImGui::Selectable("UV-Mapping Calibration"))
{
if (show_disclaimer)
{
auto disclaimer_notice = std::make_shared<ucal_disclaimer_model>();
viewer.not_model->add_notification(disclaimer_notice);
}
auto manager = std::make_shared<on_chip_calib_manager>(viewer, sub, *this, dev, sub2, sub2->uvmapping_calib_full);
auto n = std::make_shared<autocalib_notification_model>("", manager, false);
viewer.not_model->add_notification(n);
n->forced = true;
n->update_state = autocalib_notification_model::RS2_CALIB_STATE_UVMAPPING_INPUT;
for (auto&& n : related_notifications)
if (dynamic_cast<autocalib_notification_model*>(n.get()))
n->dismiss(false);
related_notifications.push_back(n);
manager->start_uvmapping_viewer();
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("UV-Mapping calibration is used to improve UV-Mapping with specific target.");
}
}
}
catch (const error& e)
{
error_message = error_to_string(e);
}
catch (const std::exception& e)
{
error_message = e.what();
}
#endif //UVMAP_CAL
//if (ImGui::Selectable("Focal Length Plus Calibration"))
//{
// try
// {
// std::shared_ptr< subdevice_model> sub_color;
// for (auto&& sub2 : subdevices)
// {
// if (sub2->s->is<rs2::color_sensor>())
// {
// sub_color = sub2;
// break;
// }
// }
// auto manager = std::make_shared<on_chip_calib_manager>(viewer, sub, *this, dev, sub_color);
// auto n = std::make_shared<autocalib_notification_model>("", manager, false);
// viewer.not_model->add_notification(n);
// n->forced = true;
// n->update_state = autocalib_notification_model::RS2_CALIB_STATE_FL_PLUS_INPUT;
// for (auto&& n : related_notifications)
// if (dynamic_cast<autocalib_notification_model*>(n.get()))
// n->dismiss(false);
// related_notifications.push_back(n);
// manager->start_fl_plus_viewer();
// }
// catch (const error& e)
// {
// error_message = error_to_string(e);
// }
// catch (const std::exception& e)
// {
// error_message = e.what();
// }
//}
//if (ImGui::IsItemHovered())
// ImGui::SetTooltip("Focal length plus calibration is used to adjust camera focal length and principal points with specific target.");
if (_calib_model.supports())
{
if (ImGui::Selectable("Calibration Data"))
{
_calib_model.open();
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Access low level camera calibration parameters");
}
if (auto fwlogger = dev.as<rs2::firmware_logger>())
{
if (ImGui::Selectable("Recover Logs from Flash"))
{
try
{
bool has_parser = false;
std::string hwlogger_xml = config_file::instance().get(configurations::viewer::hwlogger_xml);
std::ifstream f(hwlogger_xml.c_str());
if (f.good())
{
try
{
std::string str((std::istreambuf_iterator<char>(f)),
std::istreambuf_iterator<char>());
fwlogger.init_parser(str);
has_parser = true;
}
catch (const std::exception& ex)
{
viewer.not_model->output.add_log(
RS2_LOG_SEVERITY_WARN,
__FILE__,
__LINE__,
rsutils::string::from()
<< "Invalid Hardware Logger XML at '" << hwlogger_xml
<< "': " << ex.what() << "\nEither configure valid XML or remove it" );
}
}
auto message = fwlogger.create_message();
while (fwlogger.get_flash_log(message))
{
auto parsed = fwlogger.create_parsed_message();
auto parsed_ok = false;
if (has_parser)
{
if (fwlogger.parse_log(message, parsed))
{
parsed_ok = true;
viewer.not_model->output.add_log( message.get_severity(),
parsed.file_name(),
parsed.line(),
rsutils::string::from()
<< "FW-LOG [" << parsed.thread_name()
<< "] " << parsed.message() );
}
}
if (!parsed_ok)
{
std::stringstream ss;
for (auto& elem : message.data())
ss << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(elem) << " ";
viewer.not_model->output.add_log(message.get_severity(), __FILE__, 0, ss.str());
}
}
}
catch(const std::exception& ex)
{
viewer.not_model->output.add_log(
RS2_LOG_SEVERITY_WARN,
__FILE__,
__LINE__,
rsutils::string::from() << "Failed to fetch firmware logs: " << ex.what() );
}
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Recovers last set of firmware logs prior to camera shutdown / disconnect");
}
has_autocalib = true;
}
}
if (!has_autocalib)
{
bool selected = false;
something_to_show = true;
ImGui::Selectable("On-Chip Calibration", &selected, ImGuiSelectableFlags_Disabled);
ImGui::Selectable("Tare Calibration", &selected, ImGuiSelectableFlags_Disabled);
}
if (!something_to_show)
{
ImGui::Text("This device has no additional options");
}
ImGui::PopStyleColor();
ImGui::EndPopup();
}
if (keep_showing_advanced_mode_modal)
{
const bool is_advanced_mode_enabled = dev.as<advanced_mode>().is_enabled();
std::string msg = rsutils::string::from()
<< "\t\tAre you sure you want to "
<< ( is_advanced_mode_enabled ? "turn off Advanced mode" : "turn on Advanced mode" )
<< "\t\t";
keep_showing_advanced_mode_modal = prompt_toggle_advanced_mode(!is_advanced_mode_enabled, msg, restarting_device_info, viewer, window, error_message);
}
_calib_model.update(window, error_message);
////////////////////////////////////////
// Draw icons names
////////////////////////////////////////
//Move to next line, and we want to keep the horizontal alignment
ImGui::SetCursorPos({ panel_pos.x, ImGui::GetCursorPosY() });
//Using transparent-non-actionable buttons to have the same locations
ImGui::PushStyleColor(ImGuiCol_Button, ImColor(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImColor(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImColor(0, 0, 0, 0));
const ImVec2 device_panel_icons_text_size = { icons_width, 5 };
ImGui::PushStyleColor(ImGuiCol_Text, record_button_color);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, record_button_color);
ImGui::ButtonEx(is_recording ? "Stop" : "Record", device_panel_icons_size, (!is_streaming ? ImGuiButtonFlags_Disabled : 0));
if (ImGui::IsItemHovered() && is_streaming) window.link_hovered();
ImGui::PopStyleColor(2);
ImGui::SameLine(); ImGui::ButtonEx("Sync", device_panel_icons_size, ImGuiButtonFlags_Disabled);
auto info_button_color = show_device_info ? light_blue : light_grey;
ImGui::PushStyleColor(ImGuiCol_Text, info_button_color);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, info_button_color);
ImGui::SameLine(); ImGui::ButtonEx("Info", device_panel_icons_size);
if (ImGui::IsItemHovered()) window.link_hovered();
ImGui::PopStyleColor(2);
ImGui::SameLine(); ImGui::ButtonEx("More", device_panel_icons_size);
if (ImGui::IsItemHovered()) window.link_hovered();
ImGui::PopStyleColor(3);
ImGui::PopStyleVar();
ImGui::PopStyleColor(7);
ImGui::PopFont();
return device_panel_height;
}
void device_model::save_viewer_configurations(std::ofstream& outfile, json& j)
{
for (auto&& sub : subdevices)
{
int num_irs = 0;
bool video_profile_saved = false;
for (auto&& p : sub->get_selected_profiles())
{
rs2_stream stream_type = p.stream_type();
std::string stream_format_key
= rsutils::string::from()
<< "stream-" << rsutils::string::to_lower( rs2_stream_to_string( stream_type ) ) << "-format";
std::string stream_format_value = rs2_format_to_string(p.format());
if (stream_type == RS2_STREAM_DEPTH)
{
stream_format_key = "stream-depth-format";
}
else if (stream_type == RS2_STREAM_INFRARED)
{
stream_format_key = "stream-ir-format";
num_irs++;
if (num_irs == 2)
{
stream_format_value = "R8L8";
}
}
else
{
continue; //TODO: Ignoring other streams for now
}
j[stream_format_key] = stream_format_value;
if (!video_profile_saved)
{
if (auto vp = p.as<rs2::video_stream_profile>())
{
j["stream-width"] = std::to_string(vp.width());
j["stream-height"] = std::to_string(vp.height());
j["stream-fps"] = std::to_string(vp.fps());
video_profile_saved = true;
}
}
}
}
}
void rs2::device_model::handle_online_sw_update(
std::shared_ptr< notifications_model > nm,
std::shared_ptr< dev_updates_profile::update_profile > update_profile,
bool reset_delay )
{
dev_updates_profile::version_info recommended_sw_update_info;
update_profile->get_sw_update(sw_update::RECOMMENDED, recommended_sw_update_info);
auto n = std::make_shared< sw_recommended_update_alert_model >(
RS2_API_FULL_VERSION_STR,
recommended_sw_update_info.ver,
recommended_sw_update_info.download_link);
auto dev_name = get_device_name(dev);
// The SW update delay ID include the dismissed recommended version and the device serial number
// This way a newer SW recommended version will not be dismissed.
n->delay_id = "sw_update_alert." + std::string(recommended_sw_update_info.ver) + "." + dev_name.second;
n->enable_complex_dismiss = true; // allow advanced dismiss menu
if ( reset_delay ) n->reset_delay();
if (!n->is_delayed())
{
nm->add_notification(n);
related_notifications.push_back(n);
}
}
bool rs2::device_model::handle_online_fw_update(
const context & ctx,
std::shared_ptr< notifications_model > nm,
std::shared_ptr< dev_updates_profile::update_profile > update_profile,
bool reset_delay )
{
bool fw_update_notification_raised = false;
std::shared_ptr< firmware_update_manager > manager = nullptr;
std::vector< uint8_t > fw_data;
http::http_downloader downloader;
// Try to download the recommended FW binary file
int download_retries = 3;
dev_updates_profile::version_info recommended_fw_update_info;
update_profile->get_fw_update(sw_update::RECOMMENDED, recommended_fw_update_info);
while (download_retries > 0)
{
if (downloader.download_to_bytes_vector(
recommended_fw_update_info.download_link,
fw_data))
download_retries = 0;
else
--download_retries;
}
// If the download process finished successfully, pop up a
// notification for the FW update process
if (!fw_data.empty())
{
manager = std::make_shared< firmware_update_manager >(nm,
*this,
dev,
ctx,
fw_data,
true);
auto dev_name = get_device_name(dev);
std::stringstream msg;
msg << dev_name.first << " (S/N " << dev_name.second << ")\n"
<< "Current Version: "
<< std::string(update_profile->firmware_version) << "\n";
msg << "Recommended Version: "
<< std::string(recommended_fw_update_info.ver);
auto n = std::make_shared< fw_update_notification_model >(
msg.str(),
manager,
false);
n->delay_id = "fw_update_alert." + std::string(recommended_fw_update_info.ver) + "." + dev_name.second;
n->enable_complex_dismiss = true;
if ( reset_delay ) n->reset_delay();
if (!n->is_delayed())
{
nm->add_notification(n);
related_notifications.push_back(n);
fw_update_notification_raised = true;
}
}
else
nm->output.add_log( RS2_LOG_SEVERITY_WARN,
__FILE__,
__LINE__,
rsutils::string::from() << "Error in downloading FW binary file: "
<< recommended_fw_update_info.download_link );
return fw_update_notification_raised;
}
// Load viewer configuration for stereo module (depth/infrared streams) only
void device_model::load_viewer_configurations(const std::string& json_str)
{
json j;
json raw_j = json::parse(json_str);
// If the viewer contain a viewer section, look inside it for the viewer parameters,
// for backward compatibility, if no viewer section exist, look at the root level
auto viewer_section = raw_j.find("viewer");
j = (viewer_section != raw_j.end()) ? viewer_section.value() : raw_j;
struct video_stream
{
rs2_format format = RS2_FORMAT_ANY;
int width = 0;
int height = 0;
int fps = 0;
};
std::map<std::pair<rs2_stream, int>, video_stream> requested_streams;
auto it = j.find("stream-depth-format");
if (it != j.end())
{
assert(it.value().is_string());
std::string formatstr = it.value();
bool found = false;
for (int i = 0; i < static_cast<int>(RS2_FORMAT_COUNT); i++)
{
auto f = static_cast<rs2_format>(i);
auto fstr = rs2_format_to_string(f);
if (formatstr == fstr)
{
requested_streams[std::make_pair(RS2_STREAM_DEPTH, 0)].format = f;
found = true;
break;
}
}
if (!found)
{
throw std::runtime_error( rsutils::string::from() << "Unsupported stream-depth-format: " << formatstr );
}
// Disable depth stream on all sub devices
for (auto&& sub : subdevices)
{
for (auto&& profile : sub->profiles)
{
if (profile.stream_type() == RS2_STREAM_DEPTH)
{
sub->stream_enabled[profile.unique_id()] = false;
break;
}
}
}
}
it = j.find("stream-ir-format");
if (it != j.end())
{
assert(it.value().is_string());
std::string formatstr = it.value();
bool found = false;
for (int i = 0; i < RS2_FORMAT_COUNT; i++)
{
auto format = (rs2_format)i;
if (ends_with(rs2_format_to_string(format), formatstr))
{
requested_streams[std::make_pair(RS2_STREAM_INFRARED, 1)].format = format;
found = true;
}
}
if (!found)
{
if (formatstr == "R8L8")
{
requested_streams[std::make_pair(RS2_STREAM_INFRARED, 1)].format = RS2_FORMAT_Y8;
requested_streams[std::make_pair(RS2_STREAM_INFRARED, 2)].format = RS2_FORMAT_Y8;
}
else
{
throw std::runtime_error( rsutils::string::from()
<< "Unsupported stream-ir-format: " << formatstr );
}
}
// Disable infrared stream on all sub devices
for (auto&& sub : subdevices)
{
for (auto&& profile : sub->profiles)
{
if (profile.stream_type() == RS2_STREAM_INFRARED)
{
sub->stream_enabled[profile.unique_id()] = false;
break;
}
}
}
}
// Setting the same Width,Height,FPS to every requested stereo module streams (depth,infrared) according to loaded JSON
if (!requested_streams.empty())
{
try
{
std::string wstr = j["stream-width"];
std::string hstr = j["stream-height"];
std::string fstr = j["stream-fps"];
int width = std::stoi(wstr);
int height = std::stoi(hstr);
int fps = std::stoi(fstr);
for (auto& kvp : requested_streams)
{
kvp.second.width = width;
kvp.second.height = height;
kvp.second.fps = fps;
}
}
catch (const std::exception& e)
{
throw std::runtime_error( rsutils::string::from() << "Error parsing streams from JSON: " << e.what() );
}
}
// Enable requested stereo module streams (depth,infrared)
for (auto&& kvp : requested_streams)
{
std::string stream_name = rsutils::string::from()
<< rs2_stream_to_string( kvp.first.first )
<< ( kvp.first.second > 0 ? ( " " + std::to_string( kvp.first.second ) ) : "" );
for (auto&& sub : subdevices)
{
auto itr = std::find_if(sub->stream_display_names.begin(), sub->stream_display_names.end(), [stream_name](const std::pair<int, std::string>& p) { return p.second == stream_name; });
if (itr != sub->stream_display_names.end())
{
int uid = itr->first;
sub->stream_enabled[uid] = true;
//Find format
size_t format_id = 0;
rs2_format requested_format = kvp.second.format;
for (; format_id < sub->format_values[uid].size(); format_id++)
{
if (sub->format_values[uid][format_id] == requested_format)
break;
}
if (format_id == sub->format_values[uid].size())
{
throw std::runtime_error( rsutils::string::from()
<< "No match found for requested format: " << requested_format );
}
sub->ui.selected_format_id[uid] = static_cast<int>(format_id);
//Find fps
int requested_fps = kvp.second.fps;
size_t fps_id = 0;
for (; fps_id < sub->shared_fps_values.size(); fps_id++)
{
if (sub->shared_fps_values[fps_id] == requested_fps)
break;
}
if (fps_id == sub->shared_fps_values.size())
{
throw std::runtime_error( rsutils::string::from()
<< "No match found for requested fps: " << requested_fps );
}
sub->ui.selected_shared_fps_id = static_cast<int>(fps_id);
//Find Resolution
std::pair<int, int> requested_res{ kvp.second.width,kvp.second.height };
size_t res_id = 0;
for (; res_id < sub->res_values.size(); res_id++)
{
if (sub->res_values[res_id] == requested_res)
break;
}
if (res_id == sub->res_values.size())
{
throw std::runtime_error( rsutils::string::from()
<< "No match found for requested resolution: " << requested_res.first
<< "x" << requested_res.second );
}
if (!sub->ui.is_multiple_resolutions)
sub->ui.selected_res_id = static_cast<int>(res_id);
else
{
int depth_res_id, ir1_res_id, ir2_res_id;
sub->get_depth_ir_mismatch_resolutions_ids(depth_res_id, ir1_res_id, ir2_res_id);
if (kvp.first.first == RS2_STREAM_DEPTH)
sub->ui.selected_res_id_map[depth_res_id] = static_cast<int>(res_id);
else
{
sub->ui.selected_res_id_map[ir1_res_id] = static_cast<int>(res_id);
sub->ui.selected_res_id_map[ir2_res_id] = static_cast<int>(res_id);
}
}
}
}
}
}
float device_model::draw_preset_panel(float panel_width,
ux_window& window,
std::string& error_message,
viewer_model& viewer,
bool update_read_only_options,
bool load_json_if_streaming,
json_loading_func json_loading)
{
const float panel_height = 40.f;
auto panel_pos = ImGui::GetCursorPos();
ImGui::PushStyleColor(ImGuiCol_Button, sensor_bg);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, sensor_bg);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, sensor_bg);
ImGui::PushStyleColor(ImGuiCol_Text, light_grey);
ImGui::PushStyleColor(ImGuiCol_PopupBg, almost_white_bg);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, light_blue);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, light_grey);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(5, 5));
ImGui::PushFont(window.get_font());
auto serializable = dev.as<serializable_device>();
const auto load_json = [&, serializable](const std::string f) {
std::ifstream file(f);
if (!file.good())
{
//Failed to read file, removing it from the available ones
advanced_mode_settings_file_names.erase(f);
selected_file_preset.clear();
throw std::runtime_error( rsutils::string::from() << "Failed to read configuration file:\n\"" << f
<< "\"\nRemoving it from presets." );
}
std::string str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
if (serializable)
{
serializable.load_json(str);
for (auto&& sub : subdevices)
{
//If json was loaded correctly, we want the presets combo box to show the name of the configuration file
// And as a workaround, set the current preset to "custom", so that if the file is removed the preset will show "custom"
if (auto dpt = sub->s->as<depth_sensor>())
{
auto itr = sub->options_metadata.find(RS2_OPTION_VISUAL_PRESET);
if (itr != sub->options_metadata.end())
{
itr->second.endpoint->set_option(RS2_OPTION_VISUAL_PRESET, RS2_RS400_VISUAL_PRESET_CUSTOM);
}
}
}
}
load_viewer_configurations(str);
get_curr_advanced_controls = true;
advanced_mode_settings_file_names.insert(f);
selected_file_preset = f;
viewer.not_model->add_log( rsutils::string::from() << "Loaded settings from \"" << f << "\"..." );
};
const auto save_to_json = [&, serializable](std::string full_filename)
{
if (!ends_with(rsutils::string::to_lower(full_filename), ".json")) full_filename += ".json";
std::ofstream outfile(full_filename);
json j;
json *saved_configuraion = nullptr;
// place all of the viewer parameters under a viewer section
if (serializable)
{
j = json::parse(serializable.serialize_json());
j["viewer"] = json::object();
saved_configuraion = &j["viewer"]; // point to the viewer section inside the root
}
else
{
saved_configuraion = &j;
}
save_viewer_configurations(outfile, *saved_configuraion);
outfile << j.dump(4); // output all the data to the file (starting at the root)
outfile.close();
advanced_mode_settings_file_names.insert(full_filename);
selected_file_preset = full_filename;
viewer.not_model->add_log( rsutils::string::from() << "Saved settings to \"" << full_filename << "\"...");
};
static const std::string popup_message = "\t\tTo use this feature, the device must be in Advanced Mode.\t\t\n\n\t\tWould you like to turn Advanced Mode?\t\t";
////////////////////////////////////////
// Draw Combo Box
////////////////////////////////////////
for (auto&& sub : subdevices)
{
if (auto dpt = sub->s->as<depth_sensor>())
{
if (dpt.supports(RS2_OPTION_VISUAL_PRESET))
{
ImGui::SetCursorPos({ panel_pos.x + 8, ImGui::GetCursorPosY() + 10 });
//TODO: set this once!
const auto draw_preset_combo_box = [&](option_model& opt_model, std::string& error_message, notifications_model& model)
{
bool is_clicked = false;
assert(opt_model.opt == RS2_OPTION_VISUAL_PRESET);
ImGui::Text("Preset: ");
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("Select a preset configuration (or use the load button)");
}
ImGui::SameLine();
ImGui::PushItemWidth(185);
///////////////////////////////////////////
//TODO: make this a member function
int selected;
std::vector< const char * > labels = opt_model.get_combo_labels( &selected );
std::vector< float > counters;
for (auto i = opt_model.range.min; i <= opt_model.range.max; i += opt_model.range.step)
counters.push_back(i);
///////////////////////////////////////////
ImGui_ScopePushStyleColor(ImGuiCol_TextSelectedBg, white);
ImGui_ScopePushStyleColor(ImGuiCol_Button, button_color);
ImGui_ScopePushStyleColor(ImGuiCol_ButtonHovered, button_color + 0.1f);
ImGui_ScopePushStyleColor(ImGuiCol_ButtonActive, button_color + 0.1f);
ImVec2 padding{ 2,2 };
ImGui_ScopePushStyleVar(ImGuiStyleVar_FramePadding, padding);
///////////////////////////////////////////
// Go over the loaded files and add them to the combo box
std::vector<std::string> full_files_names(advanced_mode_settings_file_names.begin(), advanced_mode_settings_file_names.end());
std::vector<std::string> files_labels;
int i = static_cast<int>(labels.size());
for (auto&& file : full_files_names)
{
files_labels.push_back(get_file_name(file));
if (selected_file_preset == file)
{
selected = i;
}
i++;
}
std::transform(files_labels.begin(), files_labels.end(), std::back_inserter(labels), [](const std::string& s) { return s.c_str(); });
try
{
if (ImGui::Combo(opt_model.id.c_str(), &selected, labels.data(),
static_cast<int>(labels.size())))
{
*opt_model.invalidate_flag = true;
auto advanced = dev.as<advanced_mode>();
if (advanced)
if (!advanced.is_enabled())
sub->draw_advanced_mode_prompt = false;
if (!sub->draw_advanced_mode_prompt)
{
if (selected < static_cast<int>(labels.size() - files_labels.size()))
{
//Known preset was chosen
auto new_val = counters[selected];
model.add_log(rsutils::string::from()
<< "Setting " << opt_model.opt << " to " << new_val << " ("
<< labels[selected] << ")");
opt_model.set_option(opt_model.opt, static_cast<float>(new_val), error_message);
// Only apply preset to GUI if set_option was succesful
selected_file_preset = "";
is_clicked = true;
}
else
{
//File was chosen
auto file = selected - static_cast<int>(labels.size() - files_labels.size());
if (file < 0 || file >= full_files_names.size())
throw std::runtime_error("not a valid format");
auto f = full_files_names[file];
error_message = safe_call([&]() { load_json(f); });
selected_file_preset = f;
}
}
}
if (sub->draw_advanced_mode_prompt)
{
sub->draw_advanced_mode_prompt = prompt_toggle_advanced_mode(true, popup_message, restarting_device_info, viewer, window, error_message);
}
}
catch (const error& e)
{
error_message = error_to_string(e);
}
ImGui::PopItemWidth();
return is_clicked;
};
sub->options_metadata[RS2_OPTION_VISUAL_PRESET].custom_draw_method = draw_preset_combo_box;
if (sub->draw_option(RS2_OPTION_VISUAL_PRESET, dev.is<playback>() || update_read_only_options, error_message, *viewer.not_model))
{
get_curr_advanced_controls = true;
selected_file_preset.clear();
}
}
}
}
ImGui::SameLine();
const ImVec2 icons_size{ 20, 20 };
//TODO: Change this once we have support for loading jsons with more data than only advanced controls
bool is_streaming = std::any_of(subdevices.begin(), subdevices.end(), [](const std::shared_ptr<subdevice_model>& sm) { return sm->streaming; });
const int buttons_flags = serializable ? 0 : ImGuiButtonFlags_Disabled;
static bool require_advanced_mode_enable_prompt = false;
auto advanced_dev = dev.as<advanced_mode>();
auto is_advanced_device = false;
auto is_advanced_mode_enabled = false;
if (advanced_dev)
{
is_advanced_device = true;
try
{
// Prevent intermittent errors in polling mode to keep imgui in sync
is_advanced_mode_enabled = advanced_dev.is_enabled();
}
catch (...){}
}
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 3);
////////////////////////////////////////
// Draw Load Icon
////////////////////////////////////////
std::string upload_button_name = rsutils::string::from() << textual_icons::upload << "##" << id;
ImGui::PushStyleColor(ImGuiCol_Text, light_grey);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, light_grey);
if (ImGui::ButtonEx(upload_button_name.c_str(), icons_size, (is_streaming && !load_json_if_streaming) ? ImGuiButtonFlags_Disabled : buttons_flags))
{
if (serializable && (!is_advanced_device || is_advanced_mode_enabled))
{
json_loading([&]()
{
for( auto && sub : subdevices )
{
if( auto dpt = sub->s->as< depth_sensor >() )
{
sub->_options_invalidated = true;
}
}
auto ret = file_dialog_open(open_file, "JavaScript Object Notation (JSON | PRESET)\0*.json;*.preset\0", NULL, NULL);
if (ret)
{
error_message = safe_call([&]() { load_json(ret); });
}
});
}
else
{
require_advanced_mode_enable_prompt = true;
}
}
if (ImGui::IsItemHovered())
{
std::string tooltip = rsutils::string::from()
<< "Load pre-configured device settings"
<< ( is_streaming && ! load_json_if_streaming ? " (Disabled while streaming)" : "" );
ImGui::SetTooltip("%s", tooltip.c_str());
}
ImGui::SameLine();
////////////////////////////////////////
// Draw Save Icon
////////////////////////////////////////
std::string save_button_name = rsutils::string::from() << textual_icons::download << "##" << id;
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 1); //Align the two icons to buttom
if (ImGui::ButtonEx(save_button_name.c_str(), icons_size, buttons_flags))
{
if (serializable && (!is_advanced_device || is_advanced_mode_enabled))
{
auto ret = file_dialog_open(save_file, "JavaScript Object Notation (JSON)\0*.json\0", NULL, NULL);
if (ret)
{
error_message = safe_call([&]() { save_to_json(ret); });
}
}
else
{
require_advanced_mode_enable_prompt = true;
}
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("Save current device settings to file");
}
ImGui::PopStyleColor(2);
ImGui::SameLine();
if (require_advanced_mode_enable_prompt)
{
require_advanced_mode_enable_prompt = prompt_toggle_advanced_mode(true, popup_message, restarting_device_info, viewer, window, error_message);
}
ImGui::PopFont();
ImGui::PopStyleVar();
ImGui::PopStyleColor(7);
return panel_height;
}
bool rs2::device_model::is_streaming() const
{
return std::any_of(subdevices.begin(), subdevices.end(), [](const std::shared_ptr<subdevice_model>& sm)
{
return sm->streaming;
});
}
void device_model::draw_controls(float panel_width, float panel_height,
ux_window& window,
std::string& error_message,
device_model*& device_to_remove,
viewer_model& viewer, float windows_width,
std::vector<std::function<void()>>& draw_later,
bool load_json_if_streaming,
json_loading_func json_loading,
bool draw_device_outline)
{
////////////////////////////////////////
// draw device header
////////////////////////////////////////
const bool is_playback_device = dev.is<playback>();
bool is_ip_device = dev.supports(RS2_CAMERA_INFO_IP_ADDRESS);
auto header_h = panel_height;
if (is_playback_device || is_ip_device) header_h += 15;
ImColor device_header_background_color = title_color;
const float left_space = 3.f;
const float upper_space = 3.f;
bool update_read_only_options = false; // _update_readonly_options_timer;
const ImVec2 initial_screen_pos = ImGui::GetCursorScreenPos();
//Upper Space
ImGui::GetWindowDrawList()->AddRectFilled({ initial_screen_pos.x,initial_screen_pos.y }, { initial_screen_pos.x + panel_width,initial_screen_pos.y + upper_space }, ImColor(black));
if (draw_device_outline)
{
//Upper Line
ImGui::GetWindowDrawList()->AddLine({ initial_screen_pos.x,initial_screen_pos.y + upper_space }, { initial_screen_pos.x + panel_width,initial_screen_pos.y + upper_space }, ImColor(header_color));
}
//Device Header area
ImGui::GetWindowDrawList()->AddRectFilled({ initial_screen_pos.x + 1,initial_screen_pos.y + upper_space + 1 }, { initial_screen_pos.x + panel_width, initial_screen_pos.y + header_h + upper_space }, device_header_background_color);
auto pos = ImGui::GetCursorPos();
ImGui::PushFont(window.get_large_font());
ImGui::PushStyleColor(ImGuiCol_Button, device_header_background_color);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, device_header_background_color);
////////////////////////////////////////
// Draw device name
////////////////////////////////////////
const ImVec2 name_pos = { pos.x + 9, pos.y + 17 };
ImGui::SetCursorPos(name_pos);
std::stringstream ss;
if (dev.supports(RS2_CAMERA_INFO_NAME))
ss << dev.get_info(RS2_CAMERA_INFO_NAME);
if (is_ip_device)
{
ImGui::Text(" %s", ss.str().substr(0, ss.str().find("\n IP Device")).c_str());
ImGui::PushFont(window.get_font());
ImGui::Text("\tNetwork Device at %s", dev.get_info(RS2_CAMERA_INFO_IP_ADDRESS));
ImGui::PopFont();
}
else
{
ImGui::Text(" %s", ss.str().c_str());
if (dev.supports(RS2_CAMERA_INFO_USB_TYPE_DESCRIPTOR))
{
std::string desc = dev.get_info(RS2_CAMERA_INFO_USB_TYPE_DESCRIPTOR);
ss.str("");
ss << " " << textual_icons::usb_type << " " << desc;
ImGui::SameLine();
if (!starts_with(desc, "3.")) ImGui::PushStyleColor(ImGuiCol_Text, yellow);
else ImGui::PushStyleColor(ImGuiCol_Text, light_grey);
ImGui::Text(" %s", ss.str().c_str());
ImGui::PopStyleColor();
ss.str("");
ss << "The camera was detected by the OS as connected to a USB " << desc << " port";
ImGui::PushFont(window.get_font());
ImGui::PushStyleColor(ImGuiCol_Text, light_grey);
if (ImGui::IsItemHovered())
ImGui::SetTooltip(" %s", ss.str().c_str());
ImGui::PopStyleColor();
ImGui::PopFont();
}
else if(dev.supports(RS2_CAMERA_INFO_PRODUCT_ID))
{
std::string device_pid = dev.get_info(RS2_CAMERA_INFO_PRODUCT_ID);
if(device_pid == "ABCD")// Specific for D457
{
ss.str( "" );
ss << " " << "GMSL";
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Text, white);
ImGui::Text(" %s", ss.str().c_str());
ImGui::PopStyleColor();
}
else if(device_pid == "DDS")
{
ss.str( "" );
ss << " " << "DDS";
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Text, white);
ImGui::Text(" %s", ss.str().c_str());
ImGui::PopStyleColor();
}
}
}
//ImGui::Text(" %s", dev.get_info(RS2_CAMERA_INFO_NAME));
ImGui::PopFont();
////////////////////////////////////////
// Draw X Button
////////////////////////////////////////
ImGui::PushFont(window.get_font());
ImGui::PushStyleColor(ImGuiCol_Text, light_grey);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, light_grey);
ImGui::PushStyleColor(ImGuiCol_PopupBg, almost_white_bg);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, light_blue);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(5, 5));
if (_allow_remove)
{
ImGui::Columns(1);
float horizontal_distance_from_right_side_of_panel = 47;
ImGui::SetCursorPos({ panel_width - horizontal_distance_from_right_side_of_panel, pos.y + 9 + (header_h - panel_height) / 2 });
std::string remove_source_button_label = rsutils::string::from() << textual_icons::times << "##" << id;
if (ImGui::Button(remove_source_button_label.c_str(), { 33,35 }))
{
for (auto&& sub : subdevices)
{
if (sub->streaming)
sub->stop(viewer.not_model);
}
device_to_remove = this;
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("Remove selected device from current view\n(can be restored by clicking Add Source)");
window.link_hovered();
}
}
ImGui::PopStyleColor(4);
ImGui::PopStyleVar();
ImGui::PopFont();
////////////////////////////////////////
// Draw playback file name
////////////////////////////////////////
ImGui::SetCursorPos({ pos.x + 10, pos.y + panel_height - 9 });
if (auto p = dev.as<playback>())
{
ImGui::PushFont(window.get_font());
auto full_path = p.file_name();
auto filename = get_file_name(full_path);
std::string file_name_and_icon = rsutils::string::from() << " " << textual_icons::file_movie << " File: \"" << filename << "\"";
ImGui::Text("%s", file_name_and_icon.c_str());
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", full_path.c_str());
ImGui::PopFont();
}
ImGui::SetCursorPos({ 0, pos.y + header_h });
////////////////////////////////////////
// draw device control panel
////////////////////////////////////////
if (!is_playback_device) //Not displaying these controls for playback devices (since only info is supported)
{
pos = ImGui::GetCursorPos();
const float vertical_space_before_device_control = 10.0f;
const float horizontal_space_before_device_control = 3.0f;
auto device_panel_pos = ImVec2{ pos.x + horizontal_space_before_device_control, pos.y + vertical_space_before_device_control };
ImGui::SetCursorPos(device_panel_pos);
const float device_panel_height = draw_device_panel(panel_width, window, error_message, viewer);
ImGui::SetCursorPos({ device_panel_pos.x, device_panel_pos.y + device_panel_height });
}
////////////////////////////////////////
// draw advanced mode panel
////////////////////////////////////////
auto serialize = dev.is<serializable_device>();
if (serialize)
{
pos = ImGui::GetCursorPos();
const float vertical_space_before_advanced_mode_control = 10.0f;
const float horizontal_space_before_device_control = 3.0f;
auto advanced_mode_pos = ImVec2{ pos.x + horizontal_space_before_device_control, pos.y + vertical_space_before_advanced_mode_control };
ImGui::SetCursorPos(advanced_mode_pos);
const float advanced_mode_panel_height = draw_preset_panel(panel_width, window, error_message, viewer, update_read_only_options, load_json_if_streaming, json_loading);
ImGui::SetCursorPos({ advanced_mode_pos.x, advanced_mode_pos.y + advanced_mode_panel_height });
}
////////////////////////////////////////
// draw playback control panel
////////////////////////////////////////
if (auto p = dev.as<playback>())
{
pos = ImGui::GetCursorPos();
float space_before_playback_control = 18.0f;
auto playback_panel_pos = ImVec2{ pos.x + 10, pos.y + space_before_playback_control };
ImGui::SetCursorPos(playback_panel_pos);
auto playback_panel_height = draw_playback_panel(window, window.get_font(), viewer);
ImGui::SetCursorPos({ playback_panel_pos.x, playback_panel_pos.y + playback_panel_height });
}
bool is_streaming = std::any_of(subdevices.begin(), subdevices.end(), [](const std::shared_ptr<subdevice_model>& sm)
{
return sm->streaming;
});
pos = ImGui::GetCursorPos();
ImVec2 rc;
int info_control_panel_height = 0;
if (show_device_info)
{
ImGui::PushFont(window.get_font());
int line_h = 22;
info_control_panel_height = (int)infos.size() * line_h + 5;
for (auto&& pair : infos)
{
rc = ImGui::GetCursorPos();
ImGui::SetCursorPos({ rc.x + 12, rc.y + 4 });
std::string info_category;
if (pair.first == "Recommended Firmware Version")
{
info_category = "Min FW Version";
}
else
{
info_category = pair.first.c_str();
}
ImGui::Text("%s:", info_category.c_str());
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_FrameBg, sensor_bg);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, light_blue);
ImGui::PushStyleColor(ImGuiCol_Text, light_grey);
ImGui::SetCursorPos({ rc.x + 9.f * window.get_font_size(), rc.y + 1 });
std::string label = rsutils::string::from() << "##" << id << " " << pair.first;
ImGui::InputText(label.c_str(),
(char*)pair.second.data(),
pair.second.size() + 1,
ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_ReadOnly);
ImGui::PopStyleColor(3);
ImGui::SetCursorPos({ rc.x, rc.y + line_h });
}
ImGui::SetCursorPos({ rc.x + 225, rc.y - 107 });
ImGui::PopFont();
}
ImGui::SetCursorPos({ 0, pos.y + info_control_panel_height });
ImGui::PopStyleColor(2);
auto sensor_top_y = ImGui::GetCursorPosY();
ImGui::SetContentRegionWidth(windows_width - 36);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, sensor_bg);
ImGui::PushStyleColor(ImGuiCol_Text, light_grey);
ImGui::PushFont(window.get_font());
// Draw menu foreach subdevice with its properties
for (auto&& sub : subdevices)
{
if (show_depth_only && !sub->s->is<depth_sensor>()) continue;
const ImVec2 pos = ImGui::GetCursorPos();
//const ImVec2 abs_pos = ImGui::GetCursorScreenPos();
//model_to_y[sub.get()] = pos.y;
//model_to_abs_y[sub.get()] = abs_pos.y;
if (!show_depth_only)
{
draw_later.push_back([&error_message, windows_width, &window, sub, pos, &viewer, this]()
{
bool stop_recording = false;
ImGui::SetCursorPos({ windows_width - 42, pos.y + 3 });
ImGui_ScopePushFont(window.get_font());
ImGui_ScopePushStyleColor(ImGuiCol_Button, sensor_bg);
ImGui_ScopePushStyleColor(ImGuiCol_ButtonHovered, sensor_bg);
ImGui_ScopePushStyleColor(ImGuiCol_ButtonActive, sensor_bg);
int font_size = window.get_font_size();
ImVec2 button_size = { font_size * 1.9f, font_size * 1.9f };
if (!sub->streaming)
{
std::string label = rsutils::string::from()
<< " " << textual_icons::toggle_off << "\noff ##" << id << ","
<< sub->s->get_info( RS2_CAMERA_INFO_NAME );
ImGui_ScopePushStyleColor(ImGuiCol_Text, redish);
ImGui_ScopePushStyleColor(ImGuiCol_TextSelectedBg, redish + 0.1f);
std::vector<stream_profile> profiles;
auto is_comb_supported = sub->is_selected_combination_supported();
bool can_stream = false;
if (is_comb_supported)
can_stream = true;
else
{
profiles = sub->get_supported_profiles();
if (!profiles.empty())
can_stream = true;
else
{
std::string text = rsutils::string::from() << " " << textual_icons::toggle_off << "\noff ";
ImGui::TextDisabled("%s", text.c_str());
if (std::any_of(sub->stream_enabled.begin(), sub->stream_enabled.end(), [](std::pair<int, bool> const& s) { return s.second; }))
{
if (ImGui::IsItemHovered())
{
// ImGui::SetTooltip("Selected configuration (FPS, Resolution) is not supported");
ImGui::SetTooltip("Selected value is not supported");
}
}
else
{
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("No stream selected");
}
}
}
}
if (can_stream)
{
if( ImGui::Button( label.c_str(), button_size ) )
{
if (profiles.empty()) // profiles might be already filled
profiles = sub->get_selected_profiles();
try
{
if (!dev_syncer)
dev_syncer = viewer.syncer->create_syncer();
std::string friendly_name = sub->s->get_info(RS2_CAMERA_INFO_NAME);
if (((friendly_name.find("Tracking") != std::string::npos) ||
(friendly_name.find("Motion") != std::string::npos)))
{
viewer.synchronization_enable_prev_state = viewer.synchronization_enable.load();
viewer.synchronization_enable = false;
}
_update_readonly_options_timer.set_expired();
sub->play(profiles, viewer, dev_syncer);
}
catch (const error& e)
{
error_message = error_to_string(e);
}
catch (const std::exception& e)
{
error_message = e.what();
}
for (auto&& profile : profiles)
{
viewer.begin_stream(sub, profile);
}
}
if (ImGui::IsItemHovered())
{
window.link_hovered();
ImGui::SetTooltip("Start streaming data from this sensor");
}
}
}
else
{
std::string label = rsutils::string::from()
<< " " << textual_icons::toggle_on << "\n on##" << id << ","
<< sub->s->get_info( RS2_CAMERA_INFO_NAME );
ImGui_ScopePushStyleColor(ImGuiCol_Text, light_blue);
ImGui_ScopePushStyleColor(ImGuiCol_TextSelectedBg, light_blue + 0.1f);
if( ImGui::Button( label.c_str(), button_size ) )
{
sub->stop(viewer.not_model);
std::string friendly_name = sub->s->get_info(RS2_CAMERA_INFO_NAME);
if ((friendly_name.find("Tracking") != std::string::npos) ||
(friendly_name.find("Motion") != std::string::npos))
{
viewer.synchronization_enable = viewer.synchronization_enable_prev_state.load();
}
if (!std::any_of(subdevices.begin(), subdevices.end(),
[](const std::shared_ptr<subdevice_model>& sm)
{
return sm->streaming;
}))
{
stop_recording = true;
_update_readonly_options_timer.set_expired();
}
}
if (ImGui::IsItemHovered())
{
window.link_hovered();
ImGui::SetTooltip("Stop streaming data from selected sub-device");
}
}
if (is_recording && stop_recording)
{
this->stop_recording(viewer);
for (auto&& sub : subdevices)
{
//TODO: Fix case where sensor X recorded stream 0, then stopped, and then started recording stream 1 (need 2 sensors for this to happen)
if (sub->is_selected_combination_supported())
{
auto profiles = sub->get_selected_profiles();
for (auto&& profile : profiles)
{
viewer.streams[profile.unique_id()].dev = sub;
}
}
}
}
});
}
//ImGui::GetWindowDrawList()->AddLine({ abs_pos.x, abs_pos.y - 1 },
//{ abs_pos.x + panel_width, abs_pos.y - 1 },
// ImColor(black), 1.f);
std::string label = rsutils::string::from() << sub->s->get_info(RS2_CAMERA_INFO_NAME) << "##" << id;
ImGui::PushStyleColor(ImGuiCol_Header, sensor_bg);
ImGui::PushStyleColor(ImGuiCol_HeaderActive, sensor_bg);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, sensor_bg);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 10, 10 });
ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, { 0, 0 });
ImGuiTreeNodeFlags flags{};
if (show_depth_only) flags = ImGuiTreeNodeFlags_DefaultOpen;
if (ImGui::TreeNodeEx(label.c_str(), flags))
{
ImGui::PopStyleVar();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 2, 2 });
if (show_stream_selection)
sub->draw_stream_selection(error_message);
static const std::vector<rs2_option> drawing_order = serialize ?
std::vector<rs2_option>{ RS2_OPTION_EMITTER_ENABLED, RS2_OPTION_ENABLE_AUTO_EXPOSURE, RS2_OPTION_DEPTH_AUTO_EXPOSURE_MODE }
: std::vector<rs2_option>{ RS2_OPTION_VISUAL_PRESET, RS2_OPTION_EMITTER_ENABLED, RS2_OPTION_ENABLE_AUTO_EXPOSURE, RS2_OPTION_DEPTH_AUTO_EXPOSURE_MODE };
for (auto& opt : drawing_order)
{
if (sub->draw_option(opt, dev.is<playback>() || update_read_only_options, error_message, *viewer.not_model))
{
get_curr_advanced_controls = true;
selected_file_preset.clear();
}
}
if (sub->num_supported_non_default_options())
{
label = rsutils::string::from() << "Controls ##" << sub->s->get_info(RS2_CAMERA_INFO_NAME) << "," << id;
if (ImGui::TreeNode(label.c_str()))
{
auto const & supported_options = sub->options_metadata;
// moving the color dedicated options to the end of the vector
std::vector<rs2_option> color_options = {
RS2_OPTION_BACKLIGHT_COMPENSATION,
RS2_OPTION_BRIGHTNESS,
RS2_OPTION_CONTRAST,
RS2_OPTION_GAMMA,
RS2_OPTION_HUE,
RS2_OPTION_SATURATION,
RS2_OPTION_SHARPNESS,
RS2_OPTION_ENABLE_AUTO_WHITE_BALANCE,
RS2_OPTION_WHITE_BALANCE
};
std::vector<rs2_option> so_ordered;
for (auto const & id_model : supported_options)
{
auto it = find( color_options.begin(), color_options.end(), id_model.first );
if (it == color_options.end())
so_ordered.push_back( id_model.first );
}
std::for_each( color_options.begin(),
color_options.end(),
[&]( rs2_option opt )
{
auto it = supported_options.find( opt );
if( it != supported_options.end() )
so_ordered.push_back( opt );
} );
for (auto opt : so_ordered)
{
if( viewer.is_option_skipped( opt ) )
continue;
if (std::find(drawing_order.begin(), drawing_order.end(), opt) == drawing_order.end())
{
if (serialize && opt == RS2_OPTION_VISUAL_PRESET)
continue;
if (sub->draw_option(opt, dev.is<playback>() || update_read_only_options, error_message, *viewer.not_model))
{
get_curr_advanced_controls = true;
selected_file_preset.clear();
}
}
}
ImGui::TreePop();
}
}
if (dev.is<advanced_mode>() && sub->s->is<depth_sensor>())
{
if (draw_advanced_controls(viewer, window, error_message))
{
sub->_options_invalidated = true;
selected_file_preset.clear();
}
}
if (sub->s->is<depth_sensor>()) {
for (auto&& pb : sub->const_effects)
{
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5);
label = rsutils::string::from() << pb->get_name() << "##" << id;
if (ImGui::TreeNode(label.c_str()))
{
pb->draw_options( viewer,
dev.is< playback >() || update_read_only_options,
false,
error_message );
ImGui::TreePop();
}
}
}
if (sub->post_processing.size() > 0)
{
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5);
const ImVec2 pos = ImGui::GetCursorPos();
draw_later.push_back([windows_width, &window, sub, pos, &viewer, this]() {
ImGui::SetCursorPos({ windows_width - 41, pos.y - 3 });
try
{
ImGui::PushFont(window.get_font());
ImGui::PushStyleColor(ImGuiCol_Button, sensor_bg);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, sensor_bg);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, sensor_bg);
if (!sub->post_processing_enabled)
{
std::string label = rsutils::string::from()
<< " " << textual_icons::toggle_off << "##" << id << ","
<< sub->s->get_info( RS2_CAMERA_INFO_NAME ) << ",post";
ImGui::PushStyleColor(ImGuiCol_Text, redish);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, redish + 0.1f);
if (ImGui::Button(label.c_str(), { 30,24 }))
{
sub->post_processing_enabled = true;
config_file::instance().set(get_device_sensor_name(sub.get()).c_str(),
sub->post_processing_enabled);
for (auto&& pb : sub->post_processing)
{
if (!pb->visible)
continue;
if (pb->is_enabled())
pb->processing_block_enable_disable(true);
}
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("Enable post-processing filters");
window.link_hovered();
}
}
else
{
std::string label = rsutils::string::from()
<< " " << textual_icons::toggle_on << "##" << id << ","
<< sub->s->get_info( RS2_CAMERA_INFO_NAME ) << ",post";
ImGui::PushStyleColor(ImGuiCol_Text, light_blue);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, light_blue + 0.1f);
if (ImGui::Button(label.c_str(), { 30,24 }))
{
sub->post_processing_enabled = false;
config_file::instance().set(get_device_sensor_name(sub.get()).c_str(),
sub->post_processing_enabled);
for (auto&& pb : sub->post_processing)
{
if (!pb->visible)
continue;
if (pb->is_enabled())
pb->processing_block_enable_disable(false);
}
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("Disable post-processing filters");
window.link_hovered();
}
}
ImGui::PopStyleColor(5);
ImGui::PopFont();
}
catch (...)
{
ImGui::PopStyleColor(5);
ImGui::PopFont();
throw;
}
});
label = rsutils::string::from() << "Post-Processing##" << id;
if (ImGui::TreeNode(label.c_str()))
{
for (auto&& pb : sub->post_processing)
{
if (!pb->visible) continue;
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5);
const ImVec2 pos = ImGui::GetCursorPos();
draw_later.push_back([windows_width, &window, sub, pos, &viewer, this, pb]() {
ImGui::SetCursorPos({ windows_width - 42, pos.y - 3 });
try
{
ImGui::PushFont(window.get_font());
ImGui::PushStyleColor(ImGuiCol_Button, sensor_bg);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, sensor_bg);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, sensor_bg);
int font_size = window.get_font_size();
const ImVec2 button_size = { font_size * 2.f, font_size * 1.5f };
if (!sub->post_processing_enabled)
{
if (!pb->is_enabled())
{
std::string label = rsutils::string::from()
<< " " << textual_icons::toggle_off << "##" << id << ","
<< sub->s->get_info( RS2_CAMERA_INFO_NAME ) << ","
<< pb->get_name();
ImGui::PushStyleColor(ImGuiCol_Text, redish);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, redish + 0.1f);
ImGui::ButtonEx(label.c_str(), button_size, ImGuiButtonFlags_Disabled);
}
else
{
std::string label = rsutils::string::from()
<< " " << textual_icons::toggle_on << "##" << id << ","
<< sub->s->get_info( RS2_CAMERA_INFO_NAME ) << ","
<< pb->get_name();
ImGui::PushStyleColor(ImGuiCol_Text, light_blue);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, light_blue + 0.1f);
ImGui::ButtonEx(label.c_str(), button_size, ImGuiButtonFlags_Disabled);
}
}
else
{
if (!pb->is_enabled())
{
std::string label = rsutils::string::from()
<< " " << textual_icons::toggle_off << "##" << id << ","
<< sub->s->get_info( RS2_CAMERA_INFO_NAME ) << ","
<< pb->get_name();
ImGui::PushStyleColor(ImGuiCol_Text, redish);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, redish + 0.1f);
if (ImGui::Button(label.c_str(), button_size))
{
pb->enable(true);
pb->save_to_config_file();
}
if (ImGui::IsItemHovered())
{
label = rsutils::string::from() << "Enable " << pb->get_name() << " post-processing filter";
ImGui::SetTooltip("%s", label.c_str());
window.link_hovered();
}
}
else
{
std::string label = rsutils::string::from()
<< " " << textual_icons::toggle_on << "##" << id << ","
<< sub->s->get_info( RS2_CAMERA_INFO_NAME ) << ","
<< pb->get_name();
ImGui::PushStyleColor(ImGuiCol_Text, light_blue);
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, light_blue + 0.1f);
if (ImGui::Button(label.c_str(), button_size))
{
pb->enable(false);
pb->save_to_config_file();
}
if (ImGui::IsItemHovered())
{
label = rsutils::string::from()
<< "Disable " << pb->get_name() << " post-processing filter";
ImGui::SetTooltip("%s", label.c_str());
window.link_hovered();
}
}
}
ImGui::PopStyleColor(5);
ImGui::PopFont();
}
catch (...)
{
ImGui::PopStyleColor(5);
ImGui::PopFont();
throw;
}
});
label = rsutils::string::from() << pb->get_name() << "##" << id;
if (ImGui::TreeNode(label.c_str()))
{
pb->draw_options( viewer,
dev.is< playback >() || update_read_only_options,
false,
error_message );
ImGui::TreePop();
}
}
ImGui::TreePop();
}
}
ImGui::TreePop();
}
ImGui::PopStyleVar();
ImGui::PopStyleVar();
ImGui::PopStyleColor(3);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 2);
}
for (auto&& sub : subdevices)
{
sub->update(error_message, *viewer.not_model);
}
ImGui::PopStyleColor(2);
ImGui::PopFont();
auto end_screen_pos = ImGui::GetCursorScreenPos();
if (draw_device_outline)
{
//Left space
ImGui::GetWindowDrawList()->AddRectFilled({ initial_screen_pos.x,initial_screen_pos.y }, { end_screen_pos.x + left_space, end_screen_pos.y }, ImColor(black));
//Left line
ImGui::GetWindowDrawList()->AddLine({ initial_screen_pos.x + left_space,initial_screen_pos.y + upper_space }, { end_screen_pos.x + left_space, end_screen_pos.y }, ImColor(header_color));
//Right line
const float compenstaion_right = 17.f;;
ImGui::GetWindowDrawList()->AddLine({ initial_screen_pos.x + panel_width - compenstaion_right, initial_screen_pos.y + upper_space }, { end_screen_pos.x + panel_width - compenstaion_right, end_screen_pos.y }, ImColor(header_color));
//Button line
const float compenstaion_button = 1.0f;
ImGui::GetWindowDrawList()->AddLine({ end_screen_pos.x + left_space, end_screen_pos.y - compenstaion_button }, { end_screen_pos.x + left_space + panel_width, end_screen_pos.y - compenstaion_button }, ImColor(header_color));
}
}
void device_model::handle_hardware_events(const std::string& serialized_data)
{
//TODO: Move under hour glass
}
bool device_model::disable_record_button_logic(bool is_streaming, bool is_playback_device)
{
return (!is_streaming || is_playback_device);
}
std::string device_model::get_record_button_hover_text(bool is_streaming)
{
std::string record_button_hover_text;
if (!is_streaming)
{
record_button_hover_text = "Start streaming to enable recording";
}
else
{
record_button_hover_text = is_recording ? "Stop Recording" : "Start Recording";
}
return record_button_hover_text;
}
std::vector<std::pair<std::string, std::string>> get_devices_names(const device_list& list)
{
std::vector<std::pair<std::string, std::string>> device_names;
for (uint32_t i = 0; i < list.size(); i++)
{
try
{
auto dev = list[i];
device_names.push_back(get_device_name(dev)); // push name and sn to list
}
catch (...)
{
device_names.push_back(std::pair<std::string, std::string>(rsutils::string::from() << "Unknown Device #" << i, ""));
}
}
return device_names;
}
device_changes::device_changes(rs2::context& ctx)
{
_changes.emplace(rs2::device_list{}, ctx.query_devices(RS2_PRODUCT_LINE_ANY));
ctx.set_devices_changed_callback([&](event_information& info)
{
add_changes(info);
});
}
void device_changes::add_changes(const event_information& c)
{
std::lock_guard<std::mutex> lock(_mtx);
_changes.push(c);
}
bool device_changes::try_get_next_changes(event_information& removed_and_connected)
{
std::lock_guard<std::mutex> lock(_mtx);
if (_changes.empty())
return false;
removed_and_connected = std::move(_changes.front());
_changes.pop();
return true;
}
}