Merge upstream/main into belt/rebase/may-18

Reconciles the belt-printer branch with upstream PRs through #13723. Six
files had conflicts; three additional files needed manual follow-up fixes
where the auto-merge produced code that referenced upstream-renamed fields
or changed function signatures.

Notable reconciliations:
- TreeSupport.cpp: kept belt-floor early-exit branches around HEAD's
  drop-down logic, folded upstream's `(distance_to_top > 0 ? 1 : 0)`
  formula into the non-belt-floor path (upstream PR #11812). Dropped dead
  `roof_enabled`/`force_tip_to_roof` locals.
- TreeSupport3D.cpp: combined upstream's safety-offset + remove_small
  changes with HEAD's belt-floor clip in the per-slice trim loop. Dropped
  HEAD's `else` block (superseded by upstream's rewritten bottom-contact
  propagation) and re-added the belt-floor clip into the new propagation
  loop. Gated the propagation on belt printers to prevent OOM when
  belt-floor clipping produces empty initial slices.
- TriangleSelector.{cpp,hpp}: merged both new `select_patch` parameters
  (HEAD's `up_direction` and upstream's `select_partially`); body uses
  `dot(up_direction)` for the overhang angle check and forwards
  `select_partially` to `select_triangle`.
- SupportMaterial.cpp: `slicing_params.soluble_interface` →
  `zero_gap_interface_bottom` in HEAD's `detect_belt_floor_bottom_contacts`,
  matching upstream's same-purpose rename at line 2495.
- Custom.json, GCodeWriter.cpp: simple additive merges (kept entries /
  includes from both sides).

Verified by building OrcaSlicer (RelWithDebInfo) after a full deps
rebuild (Eigen v5.0.1, libigl v2.6.0 are now managed deps) and slicing
a scaled Benchy on the NORMALIZER belt-printer profile without OOM.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
harrierpigeon
2026-05-18 21:53:24 -05:00
co-authored by Claude Opus 4.7
2143 changed files with 130163 additions and 174147 deletions
+256 -257
View File
@@ -28,9 +28,8 @@
#include "Widgets/ProgressDialog.hpp"
#include "MsgDialog.hpp"
#if ENABLE_ACTUAL_SPEED_DEBUG
#define IMGUI_DEFINE_MATH_OPERATORS
#endif // ENABLE_ACTUAL_SPEED_DEBUG
#include <imgui/imgui_internal.h>
#include <glad/gl.h>
@@ -301,8 +300,6 @@ static std::string to_string(libvgcode::EGCodeExtrusionRole role)
void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode::Viewer* viewer, int canvas_width, int canvas_height, const libvgcode::EViewType& view_type)
{
static float last_window_width = 0.0f;
static size_t last_text_length = 0;
static bool properties_shown = false;
const std::string NA_TXT = _u8L("N/A");
@@ -320,199 +317,281 @@ void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.0f * m_scale);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding , ImVec2(10.f, 10.f) * m_scale);
ImGui::SetNextWindowBgAlpha(0.8f);
imgui.begin(std::string("ToolPosition"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove);
ImGui::AlignTextToFramePadding();
// ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORCA, _u8L("Position") + ":");
// ImGui::SameLine();
libvgcode::PathVertex vertex = viewer->get_current_vertex();
size_t vertex_id = viewer->get_current_vertex_id();
if (vertex.type == libvgcode::EMoveType::Seam) {
vertex_id = static_cast<size_t>(viewer->get_view_visible_range()[1]) - 1;
vertex = viewer->get_vertex_at(vertex_id);
// Seam moves may contain non-deterministic vertex data, so use the
// last visible non-seam vertex instead (except in FeatureType view)
if (view_type != libvgcode::EViewType::FeatureType && vertex.type == libvgcode::EMoveType::Seam) {
const libvgcode::Interval& visible_range = viewer->get_view_visible_range();
if (visible_range[1] > 0) {
vertex_id = static_cast<size_t>(visible_range[1]) - 1;
vertex = viewer->get_vertex_at(vertex_id);
}
}
const bool is_extrusion = vertex.is_extrusion();
const ImGuiStyle& style = ImGui::GetStyle();
char detail_buf[1024];
switch (view_type) {
case libvgcode::EViewType::FeatureType:
if (is_extrusion)
strcpy(detail_buf, to_string(vertex.role).c_str());
else if (vertex.type != libvgcode::EMoveType::Noop)
strcpy(detail_buf, to_string(vertex.type).c_str());
else
strcpy(detail_buf, NA_CSTR); // Noop moves are not shown in the "Line type" view, so show N/A for them.
break;
case libvgcode::EViewType::Height:
if (is_extrusion)
sprintf(detail_buf, "%s%.2f", _u8L("Height: ").c_str(), vertex.height);
else
sprintf(detail_buf, "%s%s", _u8L("Height: ").c_str(), NA_CSTR);
break;
case libvgcode::EViewType::Width:
if (is_extrusion)
sprintf(detail_buf, "%s%.2f", _u8L("Width: ").c_str(), vertex.width);
else
sprintf(detail_buf, "%s%s", _u8L("Width: ").c_str(), NA_CSTR);
break;
case libvgcode::EViewType::VolumetricFlowRate:
if (is_extrusion)
sprintf(detail_buf, "%s%.2f", _u8L("Flow: ").c_str(), vertex.volumetric_rate());
else
sprintf(detail_buf, "%s%s", _u8L("Flow: ").c_str(), NA_CSTR);
break;
case libvgcode::EViewType::FanSpeed:
sprintf(detail_buf, "%s%.0f", _u8L("Fan: ").c_str(), vertex.fan_speed);
break;
case libvgcode::EViewType::Temperature:
sprintf(detail_buf, "%s%.0f", _u8L("Temperature: ").c_str(), vertex.temperature);
break;
case libvgcode::EViewType::LayerTimeLinear:
case libvgcode::EViewType::LayerTimeLogarithmic:
sprintf(detail_buf, "%s%.1f", _u8L("Layer Time: ").c_str(), vertex.layer_duration);
break;
case libvgcode::EViewType::Tool:
sprintf(detail_buf, "%s%d", _u8L("Tool: ").c_str(), vertex.extruder_id + 1);
break;
case libvgcode::EViewType::ColorPrint:
sprintf(detail_buf, "%s%d", _u8L("Color: ").c_str(), vertex.color_id + 1);
break;
case libvgcode::EViewType::Acceleration:
sprintf(detail_buf, "%s%.0f", _u8L("Acceleration: ").c_str(), vertex.acceleration);
break;
case libvgcode::EViewType::Jerk:
sprintf(detail_buf, "%s%.1f", _u8L("Jerk: ").c_str(), vertex.jerk);
break;
case libvgcode::EViewType::PressureAdvance:
sprintf(detail_buf, "%s%.4f", _u8L("PA: ").c_str(), vertex.pressure_advance);
break;
default:
detail_buf[0] = '\0';
break;
}
// ORCA Moved position and information to bottom
std::vector<std::pair<std::string, std::string>> properties_rows;
float table_content_w = 0.0f;
if (properties_shown) {
float label_w = 0.0f;
float value_w = 0.0f;
properties_rows.reserve(13);
auto add_row = [&properties_rows, &label_w, &value_w](std::string label, std::string value) {
label_w = std::max(label_w, ImGui::CalcTextSize(label.c_str()).x);
value_w = std::max(value_w, ImGui::CalcTextSize(value.c_str()).x);
properties_rows.emplace_back(std::move(label), std::move(value));
};
char buff[1024];
add_row(_u8L("Type"), _u8L(to_string(vertex.type)));
add_row(_u8L("Line Type"), is_extrusion ? _u8L(to_string(vertex.role)) : NA_TXT);
if (is_extrusion) sprintf(buff, ("%.3f " + _u8L("mm")).c_str(), vertex.width); else strcpy(buff, NA_CSTR);
add_row(_u8L("Width"), buff);
if (is_extrusion) sprintf(buff, ("%.3f " + _u8L("mm")).c_str(), vertex.height); else strcpy(buff, NA_CSTR);
add_row(_u8L("Height"), buff);
sprintf(buff, "%d", vertex.layer_id + 1);
add_row(_u8L("Layer"), buff);
sprintf(buff, ("%.1f " + _u8L("mm/s")).c_str(), vertex.feedrate);
add_row(_u8L("Speed"), buff);
sprintf(buff, ("%.0f " + _u8L("mm/s²")).c_str(), vertex.acceleration);
add_row(_u8L("Acceleration"), buff);
sprintf(buff, ("%.1f " + _u8L("mm/s")).c_str(), vertex.jerk);
add_row(_u8L("Jerk"), buff);
if (is_extrusion) sprintf(buff, ("%.3f " + _u8L("mm³/s")).c_str(), vertex.volumetric_rate()); else strcpy(buff, NA_CSTR);
add_row(_u8L("Flow rate"), buff);
sprintf(buff, "%.0f %%", vertex.fan_speed);
add_row(_u8L("Fan speed"), buff);
sprintf(buff, ("%.0f " + _u8L("°C")).c_str(), vertex.temperature);
add_row(_u8L("Temperature"), buff);
sprintf(buff, "%.4f", vertex.pressure_advance);
add_row(_u8L("Pressure Advance"), buff);
const float estimated_time = viewer->get_estimated_time_at(vertex_id);
sprintf(buff, "%s (%.3fs)", get_time_dhms(estimated_time).c_str(), vertex.times[static_cast<size_t>(viewer->get_time_mode())]);
add_row(_u8L("Time"), buff);
table_content_w = 2 * std::max(label_w, value_w) + style.ItemInnerSpacing.x;
}
// "ToolPosition" layout width calculation
const float axes_spacing_x = ImGui::CalcTextSize("sp").x; // Spacing between axes labels
const float axes_width = ImGui::CalcTextSize("X 999.999").x; // Width of the longest possible axis label
const float axes_content_w = 3.0f * axes_width + 2.0f * axes_spacing_x; // Three axes
const float speed_width = ImGui::CalcTextSize((_u8L("Speed: ") + "9999 ").c_str()).x; // Width of the longest possible speed label
const float detail_width = ImGui::CalcTextSize(detail_buf).x; // Width of the detail text
const float info_content_w = speed_width + detail_width; // Speed + detail
const float info_group_w = std::max(axes_content_w, info_content_w); // The width of the group containing position and detail info, whichever is wider
const float fold_button_w = 24.0f * m_scale; // Width of the fold/unfold button
const float bottom_row_w = fold_button_w + style.ItemSpacing.x + info_group_w; // The width of the bottom row containing the fold button and the info group
float speed_profile_row_w = 0.0f;
#if ENABLE_ACTUAL_SPEED_DEBUG
const float table_btn_width = std::max(ImGui::CalcTextSize(_u8L("Hide").c_str()).x, ImGui::CalcTextSize(_u8L("Show").c_str()).x);
const float btn_padding_x = 8.0f * m_scale;
if (properties_shown) {
auto append_table_row = [](const std::string& label, std::function<void(void)> value_callback) {
// The width of the row containing the button to show/hide the actual speed profile table and its label
speed_profile_row_w = table_btn_width + btn_padding_x * 2.0f + style.ItemSpacing.x + ImGui::CalcTextSize(_u8L("Actual speed profile").c_str()).x;
}
#endif // ENABLE_ACTUAL_SPEED_DEBUG
const float properties_w = std::max(table_content_w, speed_profile_row_w); // The width of the properties section, whichever is wider between the properties table and the speed profile row
const float content_w = std::max(bottom_row_w, properties_w); // The width of the window content, whichever is wider between the bottom row and the properties table
const float window_w = std::ceil(content_w + 2.0f * style.WindowPadding.x); // Final window width with padding, rounded up for better look
// "ToolPosition" layout height calculation
const float text_h = ImGui::GetTextLineHeight();
const float item_spacing_y = style.ItemSpacing.y;
const float cell_pad_y = style.CellPadding.y;
const float window_pad_h = 2.0f * style.WindowPadding.y;
const float show_button_h = text_h + 2.0f * 3.0f * m_scale; // ImGuiStyleVar_FramePadding.y is set to 3.f * m_scale
const float main_row_h = 2.0f * text_h + item_spacing_y; // Two lines of text (position and detail) + spacing between them
const float properties_h = static_cast<float>(properties_rows.size()) * (text_h + 2.0f * cell_pad_y) + 2.0f * cell_pad_y + 1.0f + item_spacing_y // table rows
+ item_spacing_y + show_button_h // Spacing() + Show/Hide button row
+ item_spacing_y + 1.0f + style.FramePadding.y; // Spacing() + Separator() + Dummy()
const float folded_window_h = std::ceil(window_pad_h + main_row_h); // Height of the window when properties are hidden, with padding, rounded up for better look
const float unfolded_window_h = std::ceil(folded_window_h + properties_h); // Height of the window when properties are shown, with padding, rounded up for better look
const float window_h = properties_shown ? unfolded_window_h : folded_window_h; // Final window height depending on whether properties are shown or not
ImGui::SetNextWindowSize(ImVec2(window_w, window_h), ImGuiCond_Always);
imgui.begin(std::string("ToolPosition"), ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize);
ImGui::AlignTextToFramePadding();
if (properties_shown) {
auto append_table_row = [](const std::string& label, const std::string& value) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORCA, label);
ImGui::TableSetColumnIndex(1);
value_callback();
ImGuiWrapper::text(value);
};
//ImGui::Separator();
if (ImGui::BeginTable("Properties", 2)) {
char buff[1024];
append_table_row(_u8L("Type"), [&vertex]() {
ImGuiWrapper::text(_u8L(to_string(vertex.type)));
});
append_table_row(_u8L("Line Type"), [&vertex, NA_TXT]() {
std::string text;
if (vertex.is_extrusion())
text = _u8L(to_string(vertex.role));
else
text = NA_TXT;
ImGuiWrapper::text(text);
});
append_table_row(_u8L("Width"), [&vertex, &buff, NA_TXT]() {
std::string text;
if (vertex.is_extrusion()) {
sprintf(buff, ("%.3f " + _u8L("mm")).c_str(), vertex.width);
text = std::string(buff);
}
else
text = NA_TXT;
ImGuiWrapper::text(text);
});
append_table_row(_u8L("Height"), [&vertex, &buff, NA_TXT]() {
std::string text;
if (vertex.is_extrusion()) {
sprintf(buff, ("%.3f " + _u8L("mm")).c_str(), vertex.height);
text = std::string(buff);
}
else
text = NA_TXT;
ImGuiWrapper::text(text);
});
append_table_row(_u8L("Layer"), [&vertex, &buff]() {
sprintf(buff, "%d", vertex.layer_id + 1);
const std::string text = std::string(buff);
ImGuiWrapper::text(text);
});
append_table_row(_u8L("Speed"), [&vertex, &buff]() {
sprintf(buff, ("%.1f " + _u8L("mm/s")).c_str(), vertex.feedrate);
const std::string text = std::string(buff);
ImGuiWrapper::text(text);
});
append_table_row(_u8L("Acceleration"), [&vertex, &buff]() {
sprintf(buff, ("%.0f " + _u8L("mm/s²")).c_str(), vertex.acceleration);
const std::string text = std::string(buff);
ImGuiWrapper::text(text);
});
append_table_row(_u8L("Jerk"), [&vertex, &buff]() {
sprintf(buff, ("%.1f " + _u8L("mm/s")).c_str(), vertex.jerk);
const std::string text = std::string(buff);
ImGuiWrapper::text(text);
});
append_table_row(_u8L("Flow rate"), [&vertex, &buff, NA_TXT]() { // ORCA use "Flow rate" instead "Volumetric flow Rate" to make window more compact
std::string text;
if (vertex.is_extrusion()) {
sprintf(buff, ("%.3f " + _u8L("mm³/s")).c_str(), vertex.volumetric_rate());
text = std::string(buff);
}
else
text = NA_TXT;
ImGuiWrapper::text(text);
});
append_table_row(_u8L("Fan speed"), [&vertex, &buff]() {
sprintf(buff, "%.0f %%", vertex.fan_speed);
const std::string text = std::string(buff);
ImGuiWrapper::text(text);
});
append_table_row(_u8L("Temperature"), [&vertex, &buff]() {
sprintf(buff, ("%.0f " + _u8L("°C")).c_str(), vertex.temperature);
ImGuiWrapper::text(std::string(buff));
});
// ORCA: Add Pressure Advance visualization support
append_table_row(_u8L("Pressure Advance"), [&vertex, &buff]() {
sprintf(buff, "%.4f", vertex.pressure_advance);
ImGuiWrapper::text(std::string(buff));
});
append_table_row(_u8L("Time"), [viewer, &vertex, &buff, vertex_id]() {
const float estimated_time = viewer->get_estimated_time_at(vertex_id);
sprintf(buff, "%s (%.3fs)", get_time_dhms(estimated_time).c_str(), vertex.times[static_cast<size_t>(viewer->get_time_mode())]);
const std::string text = std::string(buff);
ImGuiWrapper::text(text);
});
for (const auto& row : properties_rows)
append_table_row(row.first, row.second);
ImGui::EndTable();
}
#if ENABLE_ACTUAL_SPEED_DEBUG
bool actual_speed_exist = vertex.is_extrusion() || vertex.is_travel() || vertex.is_wipe();
//if (vertex.is_extrusion() || vertex.is_travel() || vertex.is_wipe()) { // ORCA always show button to keep properties on same place
ImGui::Spacing();
//ImGuiWrapper::text(_u8L("Actual speed profile"));
//ImGui::SameLine();
static float cached_table_wnd_width = 0.0f; // ORCA: Cache the calculated window width to avoid recalculation on every frame
static float cached_table_wnd_scale = 0.0f; // ORCA: Cache the calculated window scale to avoid recalculation on every frame
ImGui::Spacing();
static bool table_shown = false;
imgui.push_confirm_button_style();
const float table_btn_width = std::max(ImGui::CalcTextSize(_u8L("Hide").c_str()).x, ImGui::CalcTextSize(_u8L("Show").c_str()).x);
float btn_padding_x = 8.0f * m_scale;
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding , 3.f * m_scale);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding , ImVec2(btn_padding_x, 3.f * m_scale));
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(.5f, .5f));
if (imgui.button((table_shown ? _L("Hide") : _L("Show")).c_str(), ImVec2(table_btn_width + btn_padding_x * 2.f, 0.f), actual_speed_exist))
table_shown = !table_shown;
ImGui::PopStyleVar(3);
imgui.pop_confirm_button_style();
ImGui::SameLine();
ImGuiWrapper::text(_u8L("Actual speed profile").c_str()); // ORCA show label and plot on external window to make main window more compact
static bool table_shown = false;
imgui.push_confirm_button_style();
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding , 3.f * m_scale);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding , ImVec2(btn_padding_x, 3.f * m_scale));
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(.5f, .5f));
if (imgui.button((table_shown ? _L("Hide") : _L("Show")).c_str(), ImVec2(table_btn_width + btn_padding_x * 2.f, 0.f), actual_speed_exist))
table_shown = !table_shown;
ImGui::PopStyleVar(3);
imgui.pop_confirm_button_style();
ImGui::SameLine();
ImGuiWrapper::text(_u8L("Actual speed profile").c_str()); // ORCA show label and plot on external window to make main window more compact
//ImGui::Separator();
//const int hover_id = m_actual_speed_imgui_widget.plot("##ActualSpeedProfile", { -1.0f, 150.0f });
if (actual_speed_exist && table_shown) {
static float table_wnd_height = 0.0f;
//const ImVec2 wnd_size = ImGui::GetWindowSize();
imgui.set_next_window_pos(ImGui::GetWindowPos().x - 5.f * m_scale /*+ wnd_size.x*/, static_cast<float>(canvas_height), ImGuiCond_Always, 1.0f, 1.0f);
//ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, wnd_size.y });
imgui.begin(std::string("ToolPositionTableWnd"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove);
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(9.f, 1.f) * m_scale);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, 0.f));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered , ImGui::GetStyle().Colors[ImGuiCol_TableHeaderBg]);
const int hover_id = m_actual_speed_imgui_widget.plot("##ActualSpeedProfile", { -1.f, 135.f * m_scale});
if (ImGui::BeginTable("ToolPositionTable", 2, ImGuiTableFlags_Borders /*| ImGuiTableFlags_ScrollY*/)) { // ORCA showing scrollbar causes expanding window
char buff[1024];
//ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible
ImGui::TableSetupColumn((_u8L("Position") + " (" + _u8L("mm") + ")").c_str());
ImGui::TableSetupColumn((_u8L("Speed") + " (" + _u8L("mm/s") + ")").c_str());
ImGui::TableHeadersRow();
int counter = 0;
for (const ActualSpeedImguiWidget::Item& item : m_actual_speed_imgui_widget.data) {
const bool highlight = hover_id >= 0 && (counter == hover_id || counter == hover_id + 1);
//if (highlight && counter == hover_id)
// ImGui::SetScrollHereY();
ImGui::TableNextRow();
const ImU32 row_bg_color = ImGui::GetColorU32(item.internal ? ImVec4(0.0f, 150.f / 255.0f, 136.0f / 255.f, 0.15f) : ImVec4(0.2f, 0.2f, 0.2f, 0.25f)); // ORCA
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, row_bg_color);
ImGui::TableSetColumnIndex(0);
sprintf(buff, "%.3f", item.pos);
imgui.text_colored(highlight ? ImGuiWrapper::COL_ORCA : ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()), buff);
ImGui::TableSetColumnIndex(1);
sprintf(buff, "%.1f", item.speed);
imgui.text_colored(highlight ? ImGuiWrapper::COL_ORCA : ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()), buff);
++counter;
}
if (actual_speed_exist && table_shown) {
const float plot_height = 135.f * m_scale; // 135 is the height of the plot without labels
// ORCA add blank rows to keep plot in same place. row count can be 9 but mostly it shows between 3 to 5
for (int id = 7 - counter; id > 0; --id) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
imgui.text_colored(ImVec4(0.f, 0.f, 0.f, 0.f), "f");
}
if (cached_table_wnd_scale != m_scale) { // ORCA
// Catch the window width to avoid recalculation on every frame,
// but recalculate it when/if scale changes.
ImGui::EndTable();
}
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(1);
const float curr_table_wnd_height = ImGui::GetWindowHeight();
if (table_wnd_height != curr_table_wnd_height) {
table_wnd_height = curr_table_wnd_height;
// require extra frame to hide the table scroll bar (bug in imgui)
imgui.set_requires_extra_frame();
}
imgui.end();
const float cell_pad_x = 9.f * m_scale; // matches ImGuiStyleVar_CellPadding.x below
const float wnd_pad_x = 2.0f * style.WindowPadding.x;
const float table_border_x = 6.0f * m_scale;
// Calculate the width needed to fully show each table column header
// but not clamping to a minimum because later the window width is clamped anyway.
const float col0_w = ImGui::CalcTextSize((_u8L("Position") + " (" + _u8L("mm") + ")").c_str()).x;
const float col1_w = ImGui::CalcTextSize((_u8L("Speed") + " (" + _u8L("mm/s") + ")").c_str()).x;
const float content_w = std::ceil(col0_w + col1_w + 4.0f * cell_pad_x + table_border_x + wnd_pad_x);
const float aspect_w = std::ceil(plot_height * (16.0f / 9.0f)); // 16:9 ratio for better look
// Try to obtain 16:9 ratio if possible, but keep enough width for content to make
// window looking good on different languages, especially with long words in table header
cached_table_wnd_width = std::max(content_w, aspect_w);
cached_table_wnd_scale = m_scale;
}
// ORCA: Pre-calculate the height of the table based on number of rows
// and clamp the final window height to keep at least the same height as ToolPosition window.
const float cell_pad_y = 1.f * m_scale; // ImGuiStyleVar_CellPadding.y below
const float row_height = ImGui::GetTextLineHeight() + 2.f * cell_pad_y;
const float rows = static_cast<float>(m_actual_speed_imgui_widget.data.size());
const float table_h = row_height * (1.0f + rows) + 2.f * m_scale; // border allowance
const float target_h = std::ceil(plot_height + table_h + 2.f * style.WindowPadding.y);
const float min_h = ImGui::GetWindowHeight(); // keep at least the same height as ToolPosition window
const float max_h = static_cast<float>(canvas_height) - 8.f * m_scale; // keep inside canvas with a small margin
const float final_h = std::clamp(target_h, min_h, max_h);
const bool needs_scroll = target_h > max_h;
const float table_view_h = std::max(1.0f, final_h - plot_height - 2.f * style.WindowPadding.y);
//const ImVec2 wnd_size = ImGui::GetWindowSize();
imgui.set_next_window_pos(ImGui::GetWindowPos().x - 5.f * m_scale /*+ wnd_size.x*/, static_cast<float>(canvas_height), ImGuiCond_Always, 1.0f, 1.0f);
ImGui::SetNextWindowSize(ImVec2(cached_table_wnd_width, final_h), ImGuiCond_Always);
//ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, wnd_size.y });
imgui.begin(std::string("ToolPositionTableWnd"), ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(9.f, 1.f) * m_scale);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, 0.f));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered , style.Colors[ImGuiCol_TableHeaderBg]);
const int hover_id = m_actual_speed_imgui_widget.plot("##ActualSpeedProfile", { -1.f, plot_height});
const ImGuiTableFlags table_flags = ImGuiTableFlags_Borders | (needs_scroll ? ImGuiTableFlags_ScrollY : 0);
if (ImGui::BeginTable("ToolPositionTable", 2, table_flags, ImVec2(0.0f, needs_scroll ? table_view_h : 0.0f))) {
char buff[1024];
if (needs_scroll)
ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible
ImGui::TableSetupColumn((_u8L("Position") + " (" + _u8L("mm") + ")").c_str());
ImGui::TableSetupColumn((_u8L("Speed") + " (" + _u8L("mm/s") + ")").c_str());
ImGui::TableHeadersRow();
int counter = 0;
for (const ActualSpeedImguiWidget::Item& item : m_actual_speed_imgui_widget.data) {
const bool highlight = hover_id >= 0 && (counter == hover_id || counter == hover_id + 1);
ImGui::TableNextRow();
const ImU32 row_bg_color = ImGui::GetColorU32(item.internal ? ImVec4(0.0f, 150.f / 255.0f, 136.0f / 255.f, 0.15f) : ImVec4(0.2f, 0.2f, 0.2f, 0.25f)); // ORCA
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, row_bg_color);
ImGui::TableSetColumnIndex(0);
sprintf(buff, "%.3f", item.pos);
imgui.text_colored(highlight ? ImGuiWrapper::COL_ORCA : ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()), buff);
ImGui::TableSetColumnIndex(1);
sprintf(buff, "%.1f", item.speed);
imgui.text_colored(highlight ? ImGuiWrapper::COL_ORCA : ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()), buff);
++counter;
}
ImGui::EndTable();
}
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(1);
imgui.end();
}
#endif // ENABLE_ACTUAL_SPEED_DEBUG
ImGui::Spacing();
ImGui::Separator();
ImGui::Dummy({0, ImGui::GetStyle().FramePadding.y});
ImGui::Dummy({0, style.FramePadding.y});
}
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding , 3.f * m_scale);
@@ -540,7 +619,7 @@ void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode
ImGui::SameLine();
if(!properties_shown)
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetStyle().FramePadding.y); // aligns button with next group
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - style.FramePadding.y); // aligns button with next group
ImGui::BeginGroup(); // group contents to make information area more compact
@@ -548,7 +627,6 @@ void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode
auto pos =vertex.position;
int max_value = std::round(std::max(std::max(pos[0], pos[1]),pos[2]));
auto precision = max_value > 9999 ? "%.1f" : max_value > 999 ? "%.2f" : "%.3f";
const float axes_width = ImGui::CalcTextSize("X 999.999sp").x;
char xBuf[32];
ImGui::TextColored(ImGuiWrapper::to_ImVec4(ColorRGBA::X()),"X ");
@@ -557,109 +635,30 @@ void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode
ImGui::Text("%s", xBuf);
char yBuf[32];
ImGui::SameLine(axes_width);
ImGui::SameLine(axes_width + axes_spacing_x);
ImGui::TextColored(ImGuiWrapper::to_ImVec4(ColorRGBA::Y()),"Y ");
ImGui::SameLine(0,0); // ignore item spacing
sprintf(yBuf, precision, pos[1]);
ImGui::Text("%s", yBuf);
char zBuf[32];
ImGui::SameLine(axes_width * 2);
ImGui::SameLine(2.0f * (axes_width + axes_spacing_x));
ImGui::TextColored(ImGuiWrapper::to_ImVec4(ColorRGBA::Z()),"Z ");
ImGui::SameLine(0,0); // ignore item spacing
sprintf(zBuf, precision, pos[2]);
ImGui::Text("%s", zBuf);
ImGui::SameLine(axes_width * 3);
ImGui::SameLine(3.0f * axes_width + 2.0f * axes_spacing_x);
ImGui::Dummy({0,0});
const bool is_extrusion = vertex.is_extrusion();
char buf[1024]; char valBuf[32]; char spdBuf[128];
char spdBuf[128];
sprintf(spdBuf, "%s%.0f ", _u8L("Speed: ").c_str(), vertex.feedrate);
const float speed_width = ImGui::CalcTextSize((_u8L("Speed: ") + "9999 ").c_str()).x;
ImGuiWrapper::text(std::string(spdBuf)); // render Speed as differrent item to keep next item in same place
switch (view_type) {
case libvgcode::EViewType::Height: {
if (is_extrusion)
sprintf(valBuf, "%.2f", vertex.height);
else
sprintf(valBuf, "%s", NA_CSTR);
sprintf(buf, "%s %s%s", buf, _u8L("Height: ").c_str(), valBuf);
break;
}
case libvgcode::EViewType::Width: {
if (is_extrusion)
sprintf(valBuf, "%.2f", vertex.width);
else
sprintf(valBuf, "%s", NA_CSTR);
sprintf(buf, "%s %s%s", buf, _u8L("Width: ").c_str(), valBuf);
break;
}
case libvgcode::EViewType::VolumetricFlowRate: {
if (is_extrusion)
sprintf(valBuf, "%.2f", vertex.volumetric_rate());
else
sprintf(valBuf, "%s", NA_CSTR);
sprintf(buf, "%s %s%s", buf, _u8L("Flow: ").c_str(), valBuf);
break;
}
case libvgcode::EViewType::FanSpeed: {
sprintf(buf, "%s %s%.0f", buf, _u8L("Fan: ").c_str(), vertex.fan_speed);
break;
}
case libvgcode::EViewType::Temperature: {
sprintf(buf, "%s %s%.0f", buf, _u8L("Temperature: ").c_str(), vertex.temperature);
break;
}
case libvgcode::EViewType::LayerTimeLinear:
case libvgcode::EViewType::LayerTimeLogarithmic: {
sprintf(buf, "%s %s%.1f", buf, _u8L("Layer Time: ").c_str(), vertex.layer_duration);
break;
}
case libvgcode::EViewType::Tool: {
sprintf(buf, "%s %s%d", buf, _u8L("Tool: ").c_str(), vertex.extruder_id + 1);
break;
}
case libvgcode::EViewType::ColorPrint: {
sprintf(buf, "%s %s%d", buf, _u8L("Color: ").c_str(), vertex.color_id + 1);
break;
}
case libvgcode::EViewType::ActualVolumetricFlowRate: {
// Don't display the actual flow, since it only gives data for the end of a segment
// sprintf(buf, "%s %s%.2f", buf, _u8L("Actual Flow: ").c_str(), vertex.actual_volumetric_rate());
sprintf(buf, "%s %s", buf, " ");
break;
}
case libvgcode::EViewType::ActualSpeed: {
sprintf(buf, "%s %s%.1f", buf, _u8L("Actual Speed: ").c_str(), vertex.actual_feedrate);
break;
}
case libvgcode::EViewType::Acceleration: {
sprintf(buf, "%s %s%.0f", buf, _u8L("Acceleration: ").c_str(), vertex.acceleration);
break;
}
case libvgcode::EViewType::Jerk: {
sprintf(buf, "%s %s%.1f", buf, _u8L("Jerk: ").c_str(), vertex.jerk);
break;
}
// ORCA: Add Pressure Advance visualization support
case libvgcode::EViewType::PressureAdvance: {
sprintf(buf, "%s %s%.4f", buf, _u8L("PA: ").c_str(), vertex.pressure_advance);
break;
}
default:
break;
}
ImGuiWrapper::text(spdBuf); // render Speed as differrent item to keep next item in same place
ImGui::SameLine(speed_width);
if (view_type == libvgcode::EViewType::FeatureType) {
ImGuiWrapper::text(vertex.is_extrusion() ? to_string(vertex.role).c_str() : NA_CSTR);
if (detail_buf[0] != '\0') { // dont render if buffer empty
ImGui::SameLine(speed_width);
ImGuiWrapper::text(detail_buf);
}
else {
ImGuiWrapper::text(std::string(buf));
}
ImGui::EndGroup();
imgui.end();