mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 10:51:22 +00:00
Merge branch 'main' into plugin-ui-1
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
#include "ConfigValueFormatter.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
#include "I18N.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "Field.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
std::string get_pure_opt_key(const std::string& opt_key)
|
||||
{
|
||||
std::string pure_key = opt_key;
|
||||
const int pos = pure_key.find("#");
|
||||
if (pos > 0)
|
||||
boost::erase_tail(pure_key, pure_key.size() - pos);
|
||||
return pure_key;
|
||||
}
|
||||
|
||||
wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConfig& config, bool is_infill, int idx)
|
||||
{
|
||||
const ConfigOptionDef& def = config.def()->options.at(opt_key);
|
||||
const std::vector<std::string>& names = def.enum_labels;//ConfigOptionEnum<T>::get_enum_names();
|
||||
int val = 0;
|
||||
|
||||
if (idx >= 0)
|
||||
val = dynamic_cast<const ConfigOptionInts*>(config.option(opt_key))->get_at(idx);
|
||||
else
|
||||
val = config.option(opt_key)->getInt();
|
||||
|
||||
// Each infill doesn't use all list of infill declared in PrintConfig.hpp.
|
||||
// So we should "convert" val to the correct one
|
||||
if (is_infill) {
|
||||
for (auto key_val : *def.enum_keys_map)
|
||||
if (int(key_val.second) == val) {
|
||||
auto it = std::find(def.enum_values.begin(), def.enum_values.end(), key_val.first);
|
||||
if (it == def.enum_values.end())
|
||||
return "";
|
||||
return from_u8(_utf8(names[it - def.enum_values.begin()]));
|
||||
}
|
||||
return _L("Undefined");
|
||||
}
|
||||
return from_u8(_utf8(names[val]));
|
||||
}
|
||||
|
||||
wxString get_full_label(const std::string& opt_key, const DynamicPrintConfig& config)
|
||||
{
|
||||
const std::string pure_key = get_pure_opt_key(opt_key);
|
||||
auto option = config.option(pure_key);
|
||||
|
||||
if (!option || option->is_nil())
|
||||
return _L("N/A");
|
||||
|
||||
const ConfigOptionDef* opt = config.def()->get(pure_key);
|
||||
return opt->full_label.empty() ? opt->label : opt->full_label;
|
||||
}
|
||||
|
||||
wxString get_string_value(const std::string& opt_key, const DynamicPrintConfig& config)
|
||||
{
|
||||
int orig_opt_idx = -1;
|
||||
int opt_idx = -1;
|
||||
int pos = opt_key.find("#");
|
||||
std::string temp_str = opt_key;
|
||||
if (pos > 0) {
|
||||
boost::erase_head(temp_str, pos + 1);
|
||||
orig_opt_idx = std::atoi(temp_str.c_str());
|
||||
}
|
||||
opt_idx = orig_opt_idx >= 0 ? orig_opt_idx : 0;
|
||||
const std::string pure_key = get_pure_opt_key(opt_key);
|
||||
auto option = config.option(pure_key);
|
||||
if (!option) {
|
||||
return _L("N/A");
|
||||
}
|
||||
auto opt_vector = dynamic_cast<const ConfigOptionVectorBase *>(option);
|
||||
|
||||
if ((option->is_scalar() && option->is_nil()) ||
|
||||
(option->is_vector() && opt_vector && opt_idx >= 0 && opt_idx < opt_vector->size() && opt_vector->is_nil(opt_idx)))
|
||||
return _L("N/A");
|
||||
|
||||
wxString out;
|
||||
|
||||
const ConfigOptionDef* opt = config.def()->get(pure_key);
|
||||
bool is_nullable = opt->nullable;
|
||||
|
||||
switch (opt->type) {
|
||||
case coInt:
|
||||
return from_u8((boost::format("%1%") % config.opt_int(pure_key)).str());
|
||||
case coInts: {
|
||||
if (is_nullable) {
|
||||
auto values = config.opt<ConfigOptionIntsNullable>(pure_key);
|
||||
if (opt_idx < values->size())
|
||||
return from_u8((boost::format("%1%") % values->get_at(opt_idx)).str());
|
||||
}
|
||||
else {
|
||||
auto values = config.opt<ConfigOptionInts>(pure_key);
|
||||
if (orig_opt_idx >= 0 && orig_opt_idx < values->size()) {
|
||||
return from_u8((boost::format("%1%") % values->get_at(opt_idx)).str());
|
||||
}
|
||||
else {
|
||||
std::string value_str;
|
||||
for (int i = 0; i < values->size(); i++) {
|
||||
value_str += std::to_string(values->get_at(i));
|
||||
if (i != values->size() - 1) {
|
||||
value_str += ",";
|
||||
}
|
||||
}
|
||||
return from_u8(value_str);
|
||||
}
|
||||
}
|
||||
return _L("Undefined");
|
||||
}
|
||||
case coBool:
|
||||
return config.opt_bool(pure_key) ? "true" : "false";
|
||||
case coBools: {
|
||||
if (is_nullable) {
|
||||
auto values = config.opt<ConfigOptionBoolsNullable>(pure_key);
|
||||
if (opt_idx < values->size())
|
||||
return values->get_at(opt_idx) ? "true" : "false";
|
||||
}
|
||||
else {
|
||||
auto values = config.opt<ConfigOptionBools>(pure_key);
|
||||
if (opt_idx < values->size())
|
||||
return values->get_at(opt_idx) ? "true" : "false";
|
||||
}
|
||||
return _L("Undefined");
|
||||
}
|
||||
case coPercent:
|
||||
return from_u8((boost::format("%1%%%") % int(config.optptr(pure_key)->getFloat())).str());
|
||||
case coPercents: {
|
||||
if (is_nullable) {
|
||||
auto values = config.opt<ConfigOptionPercentsNullable>(pure_key);
|
||||
if (opt_idx < values->size())
|
||||
return from_u8((boost::format("%1%%%") % values->get_at(opt_idx)).str());
|
||||
}
|
||||
else {
|
||||
auto values = config.opt<ConfigOptionPercents>(pure_key);
|
||||
if (opt_idx < values->size())
|
||||
return from_u8((boost::format("%1%%%") % values->get_at(opt_idx)).str());
|
||||
}
|
||||
return _L("Undefined");
|
||||
}
|
||||
case coFloat:
|
||||
return double_to_string(config.opt_float(pure_key));
|
||||
case coFloats: {
|
||||
if (is_nullable) {
|
||||
auto values = config.opt<ConfigOptionFloatsNullable>(pure_key);
|
||||
if (opt_idx < values->size())
|
||||
return double_to_string(values->get_at(opt_idx));
|
||||
}
|
||||
else {
|
||||
auto values = config.opt<ConfigOptionFloats>(pure_key);
|
||||
if (values && opt_idx < values->size())
|
||||
return double_to_string(values->get_at(opt_idx));
|
||||
}
|
||||
return _L("Undefined");
|
||||
}
|
||||
case coString:
|
||||
return from_u8(config.opt_string(pure_key));
|
||||
case coStrings: {
|
||||
const ConfigOptionStrings* strings = config.opt<ConfigOptionStrings>(pure_key);
|
||||
if (strings) {
|
||||
if (pure_key == "compatible_printers" || pure_key == "compatible_prints") {
|
||||
if (strings->empty())
|
||||
return _L("All");
|
||||
for (size_t id = 0; id < strings->size(); id++)
|
||||
out += from_u8(strings->get_at(id)) + "\n";
|
||||
out.RemoveLast(1);
|
||||
return out;
|
||||
}
|
||||
if (!strings->empty() && opt_idx < strings->values.size())
|
||||
return from_u8(strings->get_at(opt_idx));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case coFloatOrPercent: {
|
||||
const ConfigOptionFloatOrPercent* opt = config.opt<ConfigOptionFloatOrPercent>(pure_key);
|
||||
if (opt)
|
||||
out = double_to_string(opt->value) + (opt->percent ? "%" : "");
|
||||
return out;
|
||||
}
|
||||
case coEnum: {
|
||||
return get_string_from_enum(pure_key, config,
|
||||
pure_key == "top_surface_pattern" ||
|
||||
pure_key == "bottom_surface_pattern" ||
|
||||
pure_key == "internal_solid_infill_pattern" ||
|
||||
pure_key == "sparse_infill_pattern" ||
|
||||
pure_key == "ironing_pattern" ||
|
||||
pure_key == "support_ironing_pattern" ||
|
||||
pure_key == "support_pattern" ||
|
||||
pure_key == "support_interface_pattern")
|
||||
;
|
||||
}
|
||||
case coEnums: {
|
||||
return get_string_from_enum(pure_key, config,
|
||||
pure_key == "top_surface_pattern" ||
|
||||
pure_key == "bottom_surface_pattern" ||
|
||||
pure_key == "internal_solid_infill_pattern" ||
|
||||
pure_key == "sparse_infill_pattern" ||
|
||||
pure_key == "ironing_pattern" ||
|
||||
pure_key == "support_ironing_pattern" ||
|
||||
pure_key == "support_pattern" ||
|
||||
pure_key == "support_interface_pattern"
|
||||
, opt_idx);
|
||||
}
|
||||
case coPoint: {
|
||||
Vec2d val = config.opt<ConfigOptionPoint>(pure_key)->value;
|
||||
return from_u8((boost::format("[%1%]") % ConfigOptionPoint(val).serialize()).str());
|
||||
}
|
||||
case coPoints: {
|
||||
//BBS: add bed_exclude_area
|
||||
if (pure_key == "printable_area" || pure_key == "thumbnails") {
|
||||
ConfigOptionPoints points = *config.option<ConfigOptionPoints>(pure_key);
|
||||
//BuildVolume build_volume = {points.values, 0.};
|
||||
return get_thumbnails_string(points.values);
|
||||
}
|
||||
else if (pure_key == "bed_exclude_area") {
|
||||
return get_thumbnails_string(config.option<ConfigOptionPoints>(pure_key)->values);
|
||||
}
|
||||
else if (pure_key == "head_wrap_detect_zone") {
|
||||
return get_thumbnails_string(config.option<ConfigOptionPoints>(pure_key)->values);
|
||||
}
|
||||
else if (pure_key == "wrapping_exclude_area") {
|
||||
return get_thumbnails_string(config.option<ConfigOptionPoints>(pure_key)->values);
|
||||
}
|
||||
Vec2d val = config.opt<ConfigOptionPoints>(pure_key)->get_at(opt_idx);
|
||||
return from_u8((boost::format("[%1%]") % ConfigOptionPoint(val).serialize()).str());
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <wx/string.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
// Human-readable value of opt_key (may carry a "#<index>" suffix) in config.
|
||||
wxString get_string_value(const std::string& opt_key, const DynamicPrintConfig& config);
|
||||
|
||||
// Full label of opt_key; "N/A" when the option is not set.
|
||||
wxString get_full_label(const std::string& opt_key, const DynamicPrintConfig& config);
|
||||
|
||||
// Strip the "#<index>" suffix (if any) from the option key.
|
||||
std::string get_pure_opt_key(const std::string& opt_key);
|
||||
|
||||
// Localized label of the currently selected value of an enum option.
|
||||
wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConfig& config, bool is_infill = false, int idx = -1);
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
@@ -627,8 +627,10 @@ private:
|
||||
void on_button_click(wxCommandEvent &WXUNUSED(ev));
|
||||
void save_colors_to_config();
|
||||
private:
|
||||
#if !defined(__linux__) && !defined(__LINUX__)
|
||||
wxColourData* m_clrData{nullptr};
|
||||
wxColourPickerWidget* m_picker_widget{nullptr};
|
||||
#endif
|
||||
};
|
||||
|
||||
class PointCtrl : public Field {
|
||||
|
||||
@@ -1,16 +1,66 @@
|
||||
#include <wx/dcmemory.h>
|
||||
#include <wx/dcgraph.h>
|
||||
#include <wx/graphics.h>
|
||||
#include <wx/settings.h>
|
||||
#include <wx/window.h>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#include "EncodedFilament.hpp"
|
||||
#include "FilamentBitmapUtils.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "GuiColor.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "Widgets/Label.hpp"
|
||||
#include "Widgets/StateColor.hpp"
|
||||
#include "libslic3r/FilamentMixer.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// Barycentric utilities for a ternary (triangle) ratio picker.
|
||||
double tri_signed_area2(TriPoint a, TriPoint b, TriPoint c)
|
||||
{
|
||||
return (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y);
|
||||
}
|
||||
|
||||
bool tri_contains(TriPoint p, TriPoint v0, TriPoint v1, TriPoint v2)
|
||||
{
|
||||
double total = tri_signed_area2(v0, v1, v2);
|
||||
if (std::abs(total) < 1e-9) return false;
|
||||
double s0 = tri_signed_area2(p, v1, v2) / total;
|
||||
double s1 = tri_signed_area2(v0, p, v2) / total;
|
||||
double s2 = 1.0 - s0 - s1;
|
||||
return s0 >= -0.001 && s1 >= -0.001 && s2 >= -0.001;
|
||||
}
|
||||
|
||||
void tri_barycentric(TriPoint p, TriPoint v0, TriPoint v1, TriPoint v2,
|
||||
double& w0, double& w1, double& w2)
|
||||
{
|
||||
double total = std::abs(tri_signed_area2(v0, v1, v2));
|
||||
if (total < 1e-9) { w0 = w1 = w2 = 1.0 / 3.0; return; }
|
||||
w0 = std::abs(tri_signed_area2(p, v1, v2)) / total;
|
||||
w1 = std::abs(tri_signed_area2(v0, p, v2)) / total;
|
||||
w2 = 1.0 - w0 - w1;
|
||||
w0 = std::clamp(w0, 0.0, 1.0);
|
||||
w1 = std::clamp(w1, 0.0, 1.0);
|
||||
w2 = std::clamp(w2, 0.0, 1.0);
|
||||
double s = w0 + w1 + w2;
|
||||
if (s > 0) { w0 /= s; w1 /= s; w2 /= s; }
|
||||
}
|
||||
|
||||
TriPoint tri_clamp(TriPoint p, TriPoint v0, TriPoint v1, TriPoint v2)
|
||||
{
|
||||
double w0, w1, w2;
|
||||
tri_barycentric(p, v0, v1, v2, w0, w1, w2);
|
||||
return {w0 * v0.x + w1 * v1.x + w2 * v2.x,
|
||||
w0 * v0.y + w1 * v1.y + w2 * v2.y};
|
||||
}
|
||||
|
||||
void fill_gradient_rect_east(wxDC& dc, const wxRect& rect, const wxColour& from, const wxColour& to)
|
||||
{
|
||||
if (rect.width <= 0 || rect.height <= 0) return;
|
||||
@@ -73,7 +123,7 @@ std::vector<wxColour> sample_gradient_ramp(const wxColour& first,
|
||||
// Resolve the curve a gradient slot is sampled with, mirroring the slicer's fallback in
|
||||
// ToolOrdering: a custom curve wins, otherwise a straight line between gradient_range's
|
||||
// endpoints, otherwise the 0.10 -> 0.90 default.
|
||||
static Slic3r::GradientCurve mixed_gradient_curve(const Slic3r::DynamicPrintConfig& cfg, size_t slot)
|
||||
Slic3r::GradientCurve mixed_gradient_curve(const Slic3r::DynamicPrintConfig& cfg, size_t slot)
|
||||
{
|
||||
const auto* curve_opt = cfg.option<ConfigOptionStrings>("filament_mixed_gradient_curve");
|
||||
if (curve_opt && slot < curve_opt->values.size() && !curve_opt->values[slot].empty()) {
|
||||
@@ -449,4 +499,378 @@ void recompute_mixed_slot_colors(std::vector<wxColour>& colors,
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Layout ratios of the gradient plot rect, copied from GradientCurveEditor so the read-only
|
||||
// preview and the interactive editor stay pixel-identical. Plot rect is square 1:1; the
|
||||
// right/bottom margins host the axis arrows and labels.
|
||||
constexpr double kPlotLeftRatio = 0.0316;
|
||||
constexpr double kPlotRightRatio = 0.6766;
|
||||
constexpr double kPlotTopRatio = 0.1529;
|
||||
constexpr double kPlotBottomRatio = 0.8474;
|
||||
constexpr int kGridDivisions = 9; // 10 grid lines including the outer borders.
|
||||
constexpr int kStrokeAxis = 2; // axis line width (px, no DPI scaling)
|
||||
constexpr int kAxisArrowHalf = 5; // half-base of the axis arrow triangle (DIP)
|
||||
constexpr int kAxisArrowLen = 10; // length of the axis arrow triangle (DIP)
|
||||
constexpr int kPointRadius = 4; // anchor outer radius (DIP)
|
||||
constexpr float kBgSimilarThreshold = 15.0f;
|
||||
constexpr int kOutlineExtraDip = 2;
|
||||
constexpr double kTriangleMarginDip = 20.0;
|
||||
|
||||
// Quadratic blend that never goes out of gamut, matching MixedFilamentDialog::blend_colors.
|
||||
wxColour lerp_blend(const wxColour& a, const wxColour& b, double ratio_a)
|
||||
{
|
||||
unsigned char r, g, bl;
|
||||
Slic3r::filament_mixer_lerp(a.Red(), a.Green(), a.Blue(),
|
||||
b.Red(), b.Green(), b.Blue(),
|
||||
static_cast<float>(1.0 - ratio_a), &r, &g, &bl);
|
||||
return wxColour(r, g, bl);
|
||||
}
|
||||
|
||||
// DIP conversion for these free functions: unlike the wxWindow member FromDIP, it needs the
|
||||
// window parameter explicitly; nullptr picks the app's default DPI like the Publish dialog does.
|
||||
int dip_px(int v) { return wxWindow::FromDIP(v, nullptr); }
|
||||
|
||||
} // namespace
|
||||
|
||||
wxRect mixed_gradient_plot_rect(const wxSize& sz)
|
||||
{
|
||||
const int x = static_cast<int>(std::lround(sz.x * kPlotLeftRatio));
|
||||
const int y = static_cast<int>(std::lround(sz.y * kPlotTopRatio));
|
||||
const int x2 = static_cast<int>(std::lround(sz.x * kPlotRightRatio));
|
||||
const int y2 = static_cast<int>(std::lround(sz.y * kPlotBottomRatio));
|
||||
const int side = std::max(1, std::min(x2 - x, y2 - y));
|
||||
return wxRect(x, y, side, side);
|
||||
}
|
||||
|
||||
void draw_mixed_gradient_plot(wxDC& raw_dc, const wxSize& canvas,
|
||||
const std::vector<MixedGradientCurve>& curves,
|
||||
const std::vector<wxPoint2DDouble>& anchors,
|
||||
const MixedGradientTheme& theme)
|
||||
{
|
||||
// Draw into an internal opaque buffer so wxGCDC text/curves anti-alias against a solid
|
||||
// background (never a transparent one), then blit the finished image onto the caller's
|
||||
// buffered paint DC. wxGCDC cannot wrap a generic wxDC&, so the buffer is always a
|
||||
// wxMemoryDC -- the one type wxGCDC accepts on every platform.
|
||||
if (canvas.x <= 0 || canvas.y <= 0)
|
||||
return;
|
||||
const wxRect rc = mixed_gradient_plot_rect(canvas);
|
||||
if (rc.width <= 0 || rc.height <= 0)
|
||||
return;
|
||||
|
||||
wxBitmap buf(canvas);
|
||||
wxMemoryDC memdc(buf);
|
||||
memdc.SetBackground(wxBrush(theme.background));
|
||||
memdc.Clear();
|
||||
wxGCDC dc(memdc);
|
||||
wxGraphicsContext* gc = dc.GetGraphicsContext();
|
||||
|
||||
// 10x10 light grid (10 lines including outer borders, 9 equal divisions).
|
||||
dc.SetPen(wxPen(theme.grid, 1));
|
||||
for (int i = 0; i <= kGridDivisions; ++i) {
|
||||
const int x = rc.x + rc.width * i / kGridDivisions;
|
||||
const int y = rc.y + rc.height * i / kGridDivisions;
|
||||
dc.DrawLine(x, rc.y, x, rc.y + rc.height);
|
||||
dc.DrawLine(rc.x, y, rc.x + rc.width, y);
|
||||
}
|
||||
|
||||
// Set the label font first so text width measurements drive arrow / label placement.
|
||||
wxFont label_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
label_font.SetPointSize(std::max(7, label_font.GetPointSize() - 1));
|
||||
dc.SetFont(label_font);
|
||||
|
||||
const wxString axis_y_title = _L("Material Ratio");
|
||||
const wxString axis_x_title = _L("Model Height");
|
||||
const wxString pct_text = wxT("100%");
|
||||
const wxSize x_title_sz = dc.GetTextExtent(axis_x_title);
|
||||
const wxSize y_title_sz = dc.GetTextExtent(axis_y_title);
|
||||
|
||||
wxFont strong_font = label_font;
|
||||
strong_font.SetWeight(wxFONTWEIGHT_SEMIBOLD);
|
||||
dc.SetFont(strong_font);
|
||||
const wxSize pct_text_sz = dc.GetTextExtent(pct_text);
|
||||
dc.SetFont(label_font);
|
||||
|
||||
// Axes (grey 700) with filled triangle arrows. Y-axis extends above the plot top to the
|
||||
// canvas top edge; X-axis extends past the plot right toward the canvas right edge.
|
||||
const int arrow_half = dip_px(kAxisArrowHalf);
|
||||
const int arrow_len = dip_px(kAxisArrowLen);
|
||||
dc.SetPen(wxPen(theme.axis, kStrokeAxis));
|
||||
dc.SetBrush(wxBrush(theme.axis));
|
||||
|
||||
const int y_axis_x = rc.x;
|
||||
const int y_title_pct_gap = dip_px(1);
|
||||
const int y_title_bottom_pad = dip_px(2);
|
||||
const int y_title_y = std::max(0, rc.y - y_title_sz.y - y_title_pct_gap - pct_text_sz.y - y_title_bottom_pad);
|
||||
const int y_arrow_tip_y = y_title_y;
|
||||
const int y_arrow_ty = y_arrow_tip_y + arrow_len;
|
||||
dc.DrawLine(y_axis_x, y_arrow_ty, y_axis_x, rc.y + rc.height);
|
||||
{
|
||||
wxPoint tri[3] = {
|
||||
wxPoint(y_axis_x, y_arrow_tip_y),
|
||||
wxPoint(y_axis_x - arrow_half, y_arrow_ty),
|
||||
wxPoint(y_axis_x + arrow_half, y_arrow_ty),
|
||||
};
|
||||
dc.DrawPolygon(3, tri);
|
||||
}
|
||||
|
||||
const int x_axis_y = rc.y + rc.height;
|
||||
const int x_label_gap = dip_px(4);
|
||||
const int x_edge_pad = dip_px(6);
|
||||
const int x_arrow_ideal = rc.x + rc.width + dip_px(10);
|
||||
const int x_arrow_max = canvas.x - x_title_sz.x - x_label_gap - x_edge_pad - arrow_len;
|
||||
const int x_arrow_tx = std::max(rc.x + rc.width + arrow_len, std::min(x_arrow_ideal, x_arrow_max));
|
||||
const int x_arrow_tip_x = x_arrow_tx + arrow_len;
|
||||
const int x_title_x = x_arrow_tip_x + x_label_gap;
|
||||
dc.DrawLine(rc.x, x_axis_y, x_arrow_tx, x_axis_y);
|
||||
{
|
||||
wxPoint tri[3] = {
|
||||
wxPoint(x_arrow_tip_x, x_axis_y),
|
||||
wxPoint(x_arrow_tx, x_axis_y - arrow_half),
|
||||
wxPoint(x_arrow_tx, x_axis_y + arrow_half),
|
||||
};
|
||||
dc.DrawPolygon(3, tri);
|
||||
}
|
||||
|
||||
// Labels: "Material Ratio" and the leading "100%" share the same left x; the trailing
|
||||
// "Model Height" follows the X-axis arrow tip (already clamped to make room).
|
||||
const int label_left_x = y_axis_x + dip_px(10);
|
||||
dc.SetTextForeground(theme.label);
|
||||
dc.DrawText(axis_y_title, label_left_x, y_title_y);
|
||||
|
||||
dc.SetFont(strong_font);
|
||||
dc.SetTextForeground(theme.label_strong);
|
||||
dc.DrawText(pct_text, label_left_x, y_title_y + y_title_sz.y + y_title_pct_gap);
|
||||
|
||||
dc.DrawText(pct_text, rc.x + rc.width - pct_text_sz.x, x_axis_y);
|
||||
dc.SetFont(label_font);
|
||||
dc.SetTextForeground(theme.label);
|
||||
dc.DrawText(axis_x_title, x_title_x, x_axis_y - x_title_sz.y / 2);
|
||||
|
||||
if (!gc)
|
||||
return;
|
||||
|
||||
// Outline only when the curve colour is perceptually close to the background; otherwise the
|
||||
// plain filament colour reads fine and the extra stroke would look heavy.
|
||||
auto needs_outline = [&](const wxColour& c) {
|
||||
return calc_color_distance(c, theme.background) < kBgSimilarThreshold;
|
||||
};
|
||||
|
||||
// Only the geometry goes through the graphics context: dc.DrawLines() takes integer wxPoint
|
||||
// and would quantize the curve back to whole pixels. The pen is still set on the dc, which
|
||||
// forwards it here while keeping its own cached state in sync for later dc drawing.
|
||||
auto draw_polyline = [&](const MixedGradientCurve& curve) {
|
||||
if (curve.points.size() < 2)
|
||||
return;
|
||||
dc.SetPen(wxPen(curve.colour, dip_px(curve.stroke_dip)));
|
||||
gc->StrokeLines(curve.points.size(), curve.points.data());
|
||||
};
|
||||
|
||||
for (const MixedGradientCurve& curve : curves) {
|
||||
if (needs_outline(curve.colour))
|
||||
draw_polyline({curve.points, theme.outline, curve.stroke_dip + kOutlineExtraDip});
|
||||
draw_polyline(curve);
|
||||
}
|
||||
|
||||
// Control points: hollow circle with axis-colour border, theme-aware fill, drawn with a
|
||||
// sub-pixel centre so the ring stays centred on the curve.
|
||||
if (!anchors.empty()) {
|
||||
const double r = dip_px(kPointRadius);
|
||||
dc.SetPen(wxPen(theme.axis, 1));
|
||||
dc.SetBrush(wxBrush(theme.point_fill));
|
||||
for (const wxPoint2DDouble& p : anchors)
|
||||
gc->DrawEllipse(p.m_x - r, p.m_y - r, r * 2, r * 2);
|
||||
}
|
||||
|
||||
memdc.SelectObject(wxNullBitmap);
|
||||
raw_dc.DrawBitmap(buf, 0, 0);
|
||||
}
|
||||
|
||||
void draw_mixed_ratio_blend_bar(wxDC& dc, const wxRect& rect, const wxColour& first,
|
||||
const wxColour& second, double second_fraction)
|
||||
{
|
||||
if (rect.width <= 0 || rect.height <= 0)
|
||||
return;
|
||||
for (int x = 0; x < rect.width; ++x) {
|
||||
const double t = rect.width > 1 ? double(x) / rect.width : 0.0;
|
||||
const wxColour c = lerp_blend(first, second, 1.0 - t);
|
||||
dc.SetPen(wxPen(c));
|
||||
dc.DrawLine(rect.x + x, rect.y, rect.x + x, rect.y + rect.height);
|
||||
}
|
||||
|
||||
// Fixed in both themes, like the triangle picker's drag handle: the divider is drawn over
|
||||
// blended filament colour, so it has to keep its contrast against data rather than chrome.
|
||||
const int div_x = rect.x + static_cast<int>(second_fraction * rect.width);
|
||||
dc.SetPen(wxPen(wxColour(80, 80, 80), dip_px(4)));
|
||||
dc.DrawLine(div_x, rect.y, div_x, rect.y + rect.height);
|
||||
dc.SetPen(wxPen(*wxWHITE, dip_px(2)));
|
||||
dc.DrawLine(div_x, rect.y, div_x, rect.y + rect.height);
|
||||
}
|
||||
|
||||
void draw_mixed_ratio_segments(wxDC& dc, const wxRect& rect, const std::vector<wxColour>& colours,
|
||||
const std::vector<double>& shares)
|
||||
{
|
||||
const size_t n = std::min(colours.size(), shares.size());
|
||||
if (n == 0 || rect.width <= 0 || rect.height <= 0)
|
||||
return;
|
||||
std::vector<double> norm = shares;
|
||||
double total = 0.0;
|
||||
for (double s : norm)
|
||||
total += s;
|
||||
if (total <= 0.0) {
|
||||
norm.assign(n, 1.0 / n);
|
||||
total = 1.0;
|
||||
}
|
||||
auto share_to_px = [&](double share_sum) { return rect.x + int(std::lround(share_sum / total * double(rect.width))); };
|
||||
int x0 = rect.x;
|
||||
std::vector<wxRect> segs(n);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
int x1 = rect.x + rect.width;
|
||||
if (i + 1 < n)
|
||||
x1 = share_to_px(std::accumulate(norm.begin(), norm.begin() + i + 1, 0.0));
|
||||
segs[i] = wxRect(x0, rect.y, std::max(1, x1 - x0), rect.height);
|
||||
x0 = segs[i].GetRight() + 1;
|
||||
}
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
dc.SetBrush(wxBrush(colours[i]));
|
||||
dc.DrawRectangle(segs[i]);
|
||||
}
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
dc.SetPen(wxPen(StateColor::darkModeColorFor(wxColour("#ACACAC")), 1));
|
||||
dc.DrawRectangle(rect);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct TriCacheKey
|
||||
{
|
||||
int w, h;
|
||||
int c0r, c0g, c0b, c1r, c1g, c1b, c2r, c2g, c2b;
|
||||
int bg_r, bg_g, bg_b, ol_r, ol_g, ol_b;
|
||||
bool operator<(const TriCacheKey& o) const
|
||||
{
|
||||
return std::tie(w, h, c0r, c0g, c0b, c1r, c1g, c1b, c2r, c2g, c2b, bg_r, bg_g, bg_b, ol_r, ol_g, ol_b) <
|
||||
std::tie(o.w, o.h, o.c0r, o.c0g, o.c0b, o.c1r, o.c1g, o.c1b, o.c2r, o.c2g, o.c2b, o.bg_r, o.bg_g, o.bg_b, o.ol_r, o.ol_g, o.ol_b);
|
||||
}
|
||||
};
|
||||
|
||||
std::map<TriCacheKey, wxBitmap>& tri_cache()
|
||||
{
|
||||
static std::map<TriCacheKey, wxBitmap> cache;
|
||||
return cache;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::array<TriPoint, 3> mixed_triangle_vertices(const wxSize& size, double margin_dip)
|
||||
{
|
||||
const double pw = size.GetWidth(), ph = size.GetHeight();
|
||||
const double margin = dip_px(int(margin_dip));
|
||||
const double avail = std::min(pw, ph) - 2.0 * margin;
|
||||
const double side = avail;
|
||||
const double tri_h = side * std::sqrt(3.0) / 2.0;
|
||||
const double cx = pw / 2.0;
|
||||
const double top_y = (ph - tri_h) / 2.0;
|
||||
return {{{cx, top_y}, {cx - side / 2.0, top_y + tri_h}, {cx + side / 2.0, top_y + tri_h}}};
|
||||
}
|
||||
|
||||
void draw_mixed_triangle_picker(wxDC& dc, const wxSize& size, const std::array<wxColour, 3>& colours,
|
||||
const std::array<double, 3>& weights, const MixedTriangleTheme& theme)
|
||||
{
|
||||
if (size.GetWidth() <= 0 || size.GetHeight() <= 0)
|
||||
return;
|
||||
const std::array<TriPoint, 3> v = mixed_triangle_vertices(size, kTriangleMarginDip);
|
||||
|
||||
dc.SetBrush(wxBrush(theme.background));
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
dc.DrawRectangle(0, 0, size.GetWidth(), size.GetHeight());
|
||||
|
||||
const wxColour& c0 = colours[0];
|
||||
const wxColour& c1 = colours[1];
|
||||
const wxColour& c2 = colours[2];
|
||||
const TriCacheKey key{size.GetWidth(), size.GetHeight(),
|
||||
c0.Red(), c0.Green(), c0.Blue(),
|
||||
c1.Red(), c1.Green(), c1.Blue(),
|
||||
c2.Red(), c2.Green(), c2.Blue(),
|
||||
theme.background.Red(), theme.background.Green(), theme.background.Blue(),
|
||||
theme.outline.Red(), theme.outline.Green(), theme.outline.Blue()};
|
||||
|
||||
wxBitmap& bmp = tri_cache()[key];
|
||||
if (!bmp.IsOk()) {
|
||||
bmp = wxBitmap(size.GetWidth(), size.GetHeight(), 24);
|
||||
wxMemoryDC mdc(bmp);
|
||||
mdc.SetBrush(wxBrush(theme.background));
|
||||
mdc.SetPen(*wxTRANSPARENT_PEN);
|
||||
mdc.DrawRectangle(0, 0, size.GetWidth(), size.GetHeight());
|
||||
|
||||
const int min_y = int(std::min({v[0].y, v[1].y, v[2].y}));
|
||||
const int max_y = int(std::max({v[0].y, v[1].y, v[2].y}));
|
||||
const int min_x = int(std::min({v[0].x, v[1].x, v[2].x}));
|
||||
const int max_x = int(std::max({v[0].x, v[1].x, v[2].x}));
|
||||
for (int py = min_y; py <= max_y; ++py) {
|
||||
for (int px = min_x; px <= max_x; ++px) {
|
||||
const TriPoint p = {double(px), double(py)};
|
||||
if (!tri_contains(p, v[0], v[1], v[2]))
|
||||
continue;
|
||||
double w0, w1, w2;
|
||||
tri_barycentric(p, v[0], v[1], v[2], w0, w1, w2);
|
||||
unsigned char mr, mg, mb;
|
||||
if (w0 + w1 > 1e-6) {
|
||||
float t01 = float(w1 / (w0 + w1));
|
||||
Slic3r::filament_mixer_lerp(c0.Red(), c0.Green(), c0.Blue(), c1.Red(), c1.Green(), c1.Blue(), t01, &mr, &mg, &mb);
|
||||
Slic3r::filament_mixer_lerp(mr, mg, mb, c2.Red(), c2.Green(), c2.Blue(), float(w2), &mr, &mg, &mb);
|
||||
} else {
|
||||
mr = c2.Red(); mg = c2.Green(); mb = c2.Blue();
|
||||
}
|
||||
mdc.SetPen(wxPen(wxColour(mr, mg, mb)));
|
||||
mdc.DrawPoint(px, py);
|
||||
}
|
||||
}
|
||||
|
||||
mdc.SetPen(wxPen(theme.outline, 1));
|
||||
mdc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
const wxPoint pts[3] = {{int(v[0].x), int(v[0].y)}, {int(v[1].x), int(v[1].y)}, {int(v[2].x), int(v[2].y)}};
|
||||
mdc.DrawPolygon(3, pts);
|
||||
mdc.SelectObject(wxNullBitmap);
|
||||
|
||||
// Keep the cache from growing without bound across DPI/size changes.
|
||||
if (tri_cache().size() > 6) {
|
||||
auto& cache = tri_cache();
|
||||
cache.erase(cache.begin());
|
||||
}
|
||||
}
|
||||
dc.DrawBitmap(bmp, 0, 0);
|
||||
|
||||
// Published-ratio marker (read-only twin of the editor's drag handle).
|
||||
const double w0 = weights[0], w1 = weights[1], w2 = weights[2];
|
||||
const int hx = int(w0 * v[0].x + w1 * v[1].x + w2 * v[2].x);
|
||||
const int hy = int(w0 * v[0].y + w1 * v[1].y + w2 * v[2].y);
|
||||
dc.SetBrush(*wxWHITE_BRUSH);
|
||||
dc.SetPen(wxPen(theme.ring, dip_px(2)));
|
||||
dc.DrawCircle(hx, hy, dip_px(5));
|
||||
}
|
||||
|
||||
void draw_mixed_triangle_labels(wxDC& dc, const wxSize& size, const std::array<double, 3>& weights,
|
||||
const MixedTriangleTheme& theme)
|
||||
{
|
||||
const std::array<TriPoint, 3> v = mixed_triangle_vertices(size, kTriangleMarginDip);
|
||||
dc.SetFont(::Label::Body_12);
|
||||
dc.SetTextForeground(theme.label);
|
||||
|
||||
// "Ratio" title, sitting above the top vertex.
|
||||
const wxString title = _L("Ratio");
|
||||
dc.DrawText(title, dip_px(2), std::max(0, int(v[0].y - dc.GetTextExtent(title).GetHeight() - dip_px(4))));
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
const wxString text = wxString::Format("%d%%", int(std::lround(weights[i] * 100.0)));
|
||||
const wxSize tsz = dc.GetTextExtent(text);
|
||||
int lx = int(v[i].x - tsz.GetWidth() / 2.0);
|
||||
int ly = (i == 0) ? int(v[i].y - tsz.GetHeight() - dip_px(4)) : int(v[i].y + dip_px(3));
|
||||
ly = std::clamp(ly, 0, size.GetHeight() - tsz.GetHeight());
|
||||
lx = std::clamp(lx, 0, size.GetWidth() - tsz.GetWidth());
|
||||
dc.DrawText(text, lx, ly);
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
@@ -5,6 +5,9 @@
|
||||
#include <wx/colour.h>
|
||||
#include <wx/dc.h>
|
||||
#include <wx/gdicmn.h>
|
||||
#include <wx/geometry.h>
|
||||
#include <wx/graphics.h>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
// Orca: forward-declare so the header is self-contained outside libslic3r_gui's
|
||||
@@ -13,6 +16,16 @@ namespace Slic3r { class DynamicPrintConfig; struct GradientCurve; }
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// Barycentric utilities for a ternary (triangle) ratio picker, shared by the mixed-filament
|
||||
// editor and the Publish dialog's read-only definition preview.
|
||||
struct TriPoint { double x, y; };
|
||||
|
||||
double tri_signed_area2(TriPoint a, TriPoint b, TriPoint c);
|
||||
bool tri_contains(TriPoint p, TriPoint v0, TriPoint v1, TriPoint v2);
|
||||
void tri_barycentric(TriPoint p, TriPoint v0, TriPoint v1, TriPoint v2,
|
||||
double& w0, double& w1, double& w2);
|
||||
TriPoint tri_clamp(TriPoint p, TriPoint v0, TriPoint v1, TriPoint v2);
|
||||
|
||||
// Fills a rect with a west->east linear gradient by drawing solid 1px columns.
|
||||
// Use instead of wxDC::GradientFillLinear, whose CoreGraphics (CGShading) backend
|
||||
// fails to render on some macOS builds; solid fills are unaffected.
|
||||
@@ -51,6 +64,12 @@ std::vector<wxColour> sample_gradient_ramp(const wxColour& first,
|
||||
// destination's height in pixels.
|
||||
std::vector<wxColour> mixed_gradient_ramp(const Slic3r::DynamicPrintConfig& cfg, size_t slot, int steps);
|
||||
|
||||
// Resolve the curve a gradient slot is sampled with: the custom curve wins when it has at
|
||||
// least two points, otherwise a straight line between gradient_range's endpoints, otherwise
|
||||
// the 0.10 -> 0.90 default. Mirrors the slicer's ToolOrdering fallback so every preview
|
||||
// agrees with what gets sliced. Always returns a two-point curve.
|
||||
Slic3r::GradientCurve mixed_gradient_curve(const Slic3r::DynamicPrintConfig& cfg, size_t slot);
|
||||
|
||||
// Fill rect with a ramp, ramp.front() along the bottom edge.
|
||||
void fill_gradient_ramp_rect(wxDC& dc, const wxRect& rect, const std::vector<wxColour>& ramp);
|
||||
|
||||
@@ -63,6 +82,81 @@ wxBitmap create_gradient_ramp_bitmap(const std::vector<wxColour>& ramp, const wx
|
||||
void recompute_mixed_slot_colors(std::vector<wxColour>& colors,
|
||||
const Slic3r::DynamicPrintConfig& cfg);
|
||||
|
||||
// --- Gradient plot (shared by GradientCurveEditor and the Publish dialog's read-only
|
||||
// preview). The plot is a square 1:1 rect laid out with the editor's ratios so both
|
||||
// render identically; curves are drawn as sub-pixel anti-aliased polylines through
|
||||
// wxGCDC so they never quantize to whole pixels.
|
||||
|
||||
// One curve of the plot: screen-space sub-pixel points already mapped into the plot
|
||||
// rect, the stroke colour and the stroke width in DIP.
|
||||
struct MixedGradientCurve
|
||||
{
|
||||
std::vector<wxPoint2DDouble> points;
|
||||
wxColour colour;
|
||||
int stroke_dip;
|
||||
};
|
||||
|
||||
// Theme tokens, resolved by the caller through StateColor::darkModeColorFor.
|
||||
struct MixedGradientTheme
|
||||
{
|
||||
wxColour background; // for near-background outline detection
|
||||
wxColour grid; // grid line
|
||||
wxColour axis; // axis + arrow fill
|
||||
wxColour label; // "Material Ratio" / "Model Height"
|
||||
wxColour label_strong; // "100%"
|
||||
wxColour outline; // near-background curve lift
|
||||
wxColour point_fill; // anchor fill
|
||||
};
|
||||
|
||||
// Square 1:1 plot rect inside `canvas`, using the editor's plot ratios.
|
||||
wxRect mixed_gradient_plot_rect(const wxSize& canvas);
|
||||
|
||||
// Draw the whole plot (grid, axes + arrowheads, axis labels, each curve with an optional
|
||||
// near-background outline, and anchor circles). `anchors` are empty when the caller has
|
||||
// none to show. `dc` is the caller's buffered paint DC; a wxGCDC is created inside so the
|
||||
// geometry gets anti-aliased.
|
||||
void draw_mixed_gradient_plot(wxDC& dc, const wxSize& canvas,
|
||||
const std::vector<MixedGradientCurve>& curves,
|
||||
const std::vector<wxPoint2DDouble>& anchors,
|
||||
const MixedGradientTheme& theme);
|
||||
|
||||
// --- Ratio bar (2-component continuous blend + divider, matching MixedFilamentDialog).
|
||||
// Colours blend first->second across the rect; the divider marks `second_fraction` of the
|
||||
// rect's width (the second component's share, 0..1).
|
||||
void draw_mixed_ratio_blend_bar(wxDC& dc, const wxRect& rect, const wxColour& first,
|
||||
const wxColour& second, double second_fraction);
|
||||
|
||||
// Fallback ratio bar for N>2 non-gradient slots: one solid segment per component,
|
||||
// widths proportional to shares. Label text (the "NN%" inside wide-enough segments) is
|
||||
// the caller's concern.
|
||||
void draw_mixed_ratio_segments(wxDC& dc, const wxRect& rect, const std::vector<wxColour>& colours,
|
||||
const std::vector<double>& shares);
|
||||
|
||||
// --- Triangle picker (3-component), shared by MixedFilamentDialog and the Publish preview.
|
||||
struct MixedTriangleTheme
|
||||
{
|
||||
wxColour background;
|
||||
wxColour outline; // triangle border
|
||||
wxColour ring; // drag-handle ring
|
||||
wxColour label; // "Ratio" title + per-vertex labels
|
||||
};
|
||||
|
||||
// The three vertices of the read-only/miniature triangle inside a `size` square panel,
|
||||
// with `margin_dip` inset. Order: top, bottom-left, bottom-right.
|
||||
std::array<TriPoint, 3> mixed_triangle_vertices(const wxSize& size, double margin_dip = 20.0);
|
||||
|
||||
// Draw background, the cached barycentric fill, the outline and the drag-handle marker.
|
||||
// `weights` are the three barycentric shares (sum 1). The "Ratio" title and per-vertex
|
||||
// percentage labels are drawn by the caller so the interactive editor can keep its own
|
||||
// live child labels while the read-only preview draws them as text.
|
||||
void draw_mixed_triangle_picker(wxDC& dc, const wxSize& size, const std::array<wxColour, 3>& colours,
|
||||
const std::array<double, 3>& weights, const MixedTriangleTheme& theme);
|
||||
|
||||
// Draw the "Ratio" title plus one "NN%" label per vertex (used by the read-only preview;
|
||||
// the interactive editor positions its own live labels instead).
|
||||
void draw_mixed_triangle_labels(wxDC& dc, const wxSize& size, const std::array<double, 3>& weights,
|
||||
const MixedTriangleTheme& theme);
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // slic3r_GUI_FilamentBitmapUtils_hpp_
|
||||
@@ -598,7 +598,7 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension)
|
||||
static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); }
|
||||
|
||||
#ifdef WIN32
|
||||
static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 };
|
||||
static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, { 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } };
|
||||
|
||||
static void register_win32_device_notification_event()
|
||||
{
|
||||
|
||||
@@ -69,6 +69,8 @@ CopyFileResult copy_file_gui(const std::string &from, const std::string &to, std
|
||||
HANDLE handlesrc = nullptr;
|
||||
HANDLE handledst = nullptr;
|
||||
CopyFileResult ret = SUCCESS;
|
||||
DWORD size = 0;
|
||||
DWORD dwRead = 0, dwWrite = 0;
|
||||
|
||||
handlesrc = CreateFile(src.wc_str(),
|
||||
GENERIC_READ,
|
||||
@@ -96,9 +98,8 @@ CopyFileResult copy_file_gui(const std::string &from, const std::string &to, std
|
||||
goto __finished;
|
||||
}
|
||||
|
||||
DWORD size=GetFileSize(handlesrc,NULL);
|
||||
size = GetFileSize(handlesrc,NULL);
|
||||
buff = new char[size+1];
|
||||
DWORD dwRead=0,dwWrite;
|
||||
result = ReadFile(handlesrc, buff, size, &dwRead, NULL);
|
||||
if (!result) {
|
||||
DWORD errCode = GetLastError();
|
||||
|
||||
@@ -702,7 +702,7 @@ bool GizmoObjectManipulation::reset_zero_button(ImGuiWrapper *imgui_wrapper, bo
|
||||
|
||||
for (int i = 0; i < number; i++)
|
||||
{
|
||||
char buf[3][64] = {0};
|
||||
char buf[3][64] = {};
|
||||
float buf_size[3] = {0};
|
||||
for (int j = 0; j < 3; j++) {
|
||||
ImGui::DataTypeFormatString(buf[j], IM_ARRAYSIZE(buf[j]), ImGuiDataType_Double, (void *) &vec[i][j], "%.2f");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "GradientCurveEditor.hpp"
|
||||
#include "FilamentBitmapUtils.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "GuiColor.hpp"
|
||||
#include "I18N.hpp"
|
||||
@@ -19,23 +20,13 @@ namespace GUI {
|
||||
wxDEFINE_EVENT(wxEVT_GRADIENT_CURVE_CHANGED, wxCommandEvent);
|
||||
|
||||
namespace {
|
||||
// Layout ratios of the plot rect within the widget, taken from a 214 x 180 px reference drawing.
|
||||
// Plot rect occupies the upper-left region; right + bottom margins host axis arrows / labels.
|
||||
constexpr double kPlotLeftRatio = 0.0316;
|
||||
constexpr double kPlotRightRatio = 0.6766;
|
||||
constexpr double kPlotTopRatio = 0.1529;
|
||||
constexpr double kPlotBottomRatio = 0.8474;
|
||||
constexpr int kGridDivisions = 9; // 10 grid lines including the outer borders.
|
||||
|
||||
// Hit / stroke (DIP).
|
||||
// Hit / stroke (DIP). The plot-rect ratios, grid divisions, axis/arrow geometry and the
|
||||
// near-background outline threshold now live in FilamentBitmapUtils so the read-only Publish
|
||||
// preview and this editor stay pixel-identical.
|
||||
constexpr int kHitRadius = 6;
|
||||
constexpr int kCurveHitRadius = 5;
|
||||
constexpr int kPointRadius = 4; // anchor outer radius (DIP)
|
||||
constexpr int kStrokeUnselected = 2;
|
||||
constexpr int kStrokeSelected = 4;
|
||||
constexpr int kStrokeAxis = 2; // axis line width (px, no DPI scaling - matches kGridColor pen and 2DBed convention)
|
||||
constexpr int kAxisArrowHalf = 5; // half-base of the axis arrow triangle (DIP)
|
||||
constexpr int kAxisArrowLen = 10; // length of the axis arrow triangle (DIP)
|
||||
|
||||
// Light-mode design tokens. Resolved through StateColor::darkModeColorFor()
|
||||
// at paint time so the editor follows the app theme (#EEEEEE -> #4C4C55, #6B6B6B ->
|
||||
@@ -46,12 +37,6 @@ const wxColour kAxisColor (107, 107, 107); // #6B6B6B grey 700
|
||||
const wxColour kLabelMuted (107, 107, 107); // #6B6B6B grey 700
|
||||
const wxColour kLabelStrong ( 38, 46, 48); // #262E30 grey 900
|
||||
const wxColour kOutlineColor(172, 172, 172); // #ACACAC dimmed elements
|
||||
|
||||
// LAB (DeltaE76) threshold for "curve color is too close to the background": below it the curve
|
||||
// gets a subtle outline so it does not visually vanish, otherwise it is drawn plain. Looser than
|
||||
// the 5.0 of FlushPredict::is_similar_color, so a pastel pink on white still gets an outline.
|
||||
constexpr float kBgSimilarThreshold = 15.0f;
|
||||
constexpr int kOutlineExtraDip = 2;
|
||||
} // namespace
|
||||
|
||||
GradientCurveEditor::GradientCurveEditor(wxWindow* parent,
|
||||
@@ -177,15 +162,8 @@ void GradientCurveEditor::emit_changed()
|
||||
|
||||
wxRect GradientCurveEditor::plot_rect() const
|
||||
{
|
||||
const wxSize sz = GetClientSize();
|
||||
const int x = static_cast<int>(std::lround(sz.x * kPlotLeftRatio));
|
||||
const int y = static_cast<int>(std::lround(sz.y * kPlotTopRatio));
|
||||
const int x2 = static_cast<int>(std::lround(sz.x * kPlotRightRatio));
|
||||
const int y2 = static_cast<int>(std::lround(sz.y * kPlotBottomRatio));
|
||||
// Force square 1:1 so X/Y axes share the same scale and grid cells stay square. Anchor at
|
||||
// the top-left so the "100%" labels on the bottom/right still align with the plot edges.
|
||||
const int side = std::max(1, std::min(x2 - x, y2 - y));
|
||||
return wxRect(x, y, side, side);
|
||||
// Square 1:1 plot, shared with the Publish dialog's read-only preview.
|
||||
return mixed_gradient_plot_rect(GetClientSize());
|
||||
}
|
||||
|
||||
wxPoint2DDouble GradientCurveEditor::data_to_px_f(double x, double y) const
|
||||
@@ -330,171 +308,52 @@ void GradientCurveEditor::on_paint(wxPaintEvent& /*evt*/)
|
||||
raw_dc.SetBackground(wxBrush(bg));
|
||||
raw_dc.Clear();
|
||||
|
||||
// Render through wxGCDC so curves, arrows and anchor circles get anti-aliased; the buffered
|
||||
// DC is the actual back buffer that gets blitted to the window.
|
||||
wxGCDC dc(raw_dc);
|
||||
// The curve and its anchors are drawn straight on the graphics context so their
|
||||
// coordinates stay sub-pixel accurate (see data_to_px_f).
|
||||
wxGraphicsContext* gc = dc.GetGraphicsContext();
|
||||
|
||||
const wxRect rc = plot_rect();
|
||||
if (rc.width <= 0 || rc.height <= 0)
|
||||
return;
|
||||
|
||||
// 10x10 light grid (10 lines including outer borders, 9 equal divisions).
|
||||
dc.SetPen(wxPen(grid_color, 1));
|
||||
for (int i = 0; i <= kGridDivisions; ++i) {
|
||||
const int x = rc.x + rc.width * i / kGridDivisions;
|
||||
const int y = rc.y + rc.height * i / kGridDivisions;
|
||||
dc.DrawLine(x, rc.y, x, rc.y + rc.height);
|
||||
dc.DrawLine(rc.x, y, rc.x + rc.width, y);
|
||||
}
|
||||
|
||||
// Set the label font first so text width measurements drive arrow / label placement.
|
||||
wxFont label_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
label_font.SetPointSize(std::max(7, label_font.GetPointSize() - 1));
|
||||
dc.SetFont(label_font);
|
||||
|
||||
const wxString axis_y_title = _L("Material Ratio");
|
||||
const wxString axis_x_title = _L("Model Height");
|
||||
const wxString pct_text = wxT("100%");
|
||||
const wxSize x_title_sz = dc.GetTextExtent(axis_x_title);
|
||||
const wxSize y_title_sz = dc.GetTextExtent(axis_y_title);
|
||||
|
||||
wxFont strong_font = label_font;
|
||||
strong_font.SetWeight(wxFONTWEIGHT_SEMIBOLD);
|
||||
dc.SetFont(strong_font);
|
||||
const wxSize pct_text_sz = dc.GetTextExtent(pct_text);
|
||||
dc.SetFont(label_font);
|
||||
|
||||
// Axes (grey 700) with filled triangle arrows. Y-axis extends above the plot top to the
|
||||
// canvas top edge; X-axis extends past the plot right toward the canvas right edge.
|
||||
const int arrow_half = FromDIP(kAxisArrowHalf);
|
||||
const int arrow_len = FromDIP(kAxisArrowLen);
|
||||
const wxSize sz = GetClientSize();
|
||||
dc.SetPen(wxPen(axis_color, kStrokeAxis));
|
||||
dc.SetBrush(wxBrush(axis_color));
|
||||
|
||||
// Y-axis: vertical line at plot_left, from arrow tip near canvas top down to plot bottom.
|
||||
const int y_axis_x = rc.x;
|
||||
const int y_title_pct_gap = FromDIP(1);
|
||||
const int y_title_bottom_pad = FromDIP(2);
|
||||
const int y_title_y = std::max(0, rc.y - y_title_sz.y - y_title_pct_gap - pct_text_sz.y - y_title_bottom_pad);
|
||||
const int y_arrow_tip_y = y_title_y;
|
||||
const int y_arrow_ty = y_arrow_tip_y + arrow_len;
|
||||
dc.DrawLine(y_axis_x, y_arrow_ty, y_axis_x, rc.y + rc.height);
|
||||
{
|
||||
wxPoint tri[3] = {
|
||||
wxPoint(y_axis_x, y_arrow_tip_y),
|
||||
wxPoint(y_axis_x - arrow_half, y_arrow_ty),
|
||||
wxPoint(y_axis_x + arrow_half, y_arrow_ty),
|
||||
// Render the plot (grid, axes, labels, curves, anchors) through the shared painter so the
|
||||
// interactive editor and the Publish dialog's read-only preview stay pixel-identical. The
|
||||
// curves are handed over as sub-pixel polylines and anti-alias inside the helper.
|
||||
std::vector<MixedGradientCurve> curves;
|
||||
std::vector<wxPoint2DDouble> anchors;
|
||||
if (m_points.size() >= 2) {
|
||||
auto color_for_curve = [&](int curve_idx) -> wxColour {
|
||||
wxColour c = (curve_idx == 0) ? m_color_low : m_color_high;
|
||||
// Transparent filaments (alpha == 0, e.g. #FFFFFF00) would be invisible.
|
||||
if (c.Alpha() == 0)
|
||||
c.Set(c.Red(), c.Green(), c.Blue(), 150);
|
||||
return c;
|
||||
};
|
||||
dc.DrawPolygon(3, tri);
|
||||
}
|
||||
|
||||
// X-axis arrow tip: stays just past the plot ideally, but is clamped so the trailing
|
||||
// "Material Ratio" label still fits inside the canvas without overlapping the arrow.
|
||||
const int x_axis_y = rc.y + rc.height;
|
||||
const int x_label_gap = FromDIP(4);
|
||||
const int x_edge_pad = FromDIP(6);
|
||||
const int x_arrow_ideal = rc.x + rc.width + FromDIP(10);
|
||||
const int x_arrow_max = sz.x - x_title_sz.x - x_label_gap - x_edge_pad - arrow_len;
|
||||
const int x_arrow_tx = std::max(rc.x + rc.width + arrow_len,
|
||||
std::min(x_arrow_ideal, x_arrow_max));
|
||||
const int x_arrow_tip_x = x_arrow_tx + arrow_len;
|
||||
const int x_title_x = x_arrow_tip_x + x_label_gap;
|
||||
dc.DrawLine(rc.x, x_axis_y, x_arrow_tx, x_axis_y);
|
||||
{
|
||||
wxPoint tri[3] = {
|
||||
wxPoint(x_arrow_tip_x, x_axis_y),
|
||||
wxPoint(x_arrow_tx, x_axis_y - arrow_half),
|
||||
wxPoint(x_arrow_tx, x_axis_y + arrow_half),
|
||||
auto build_polyline = [&](int curve_idx) -> std::vector<wxPoint2DDouble> {
|
||||
const int samples = std::max(128, plot_rect().width * 2);
|
||||
std::vector<wxPoint2DDouble> poly;
|
||||
poly.reserve(samples + 1);
|
||||
for (int s = 0; s <= samples; ++s) {
|
||||
const double x = double(s) / samples;
|
||||
const double y0 = sample_curve_y(x);
|
||||
const double vy = to_visual_y(curve_idx, y0);
|
||||
poly.push_back(data_to_px_f(x, vy));
|
||||
}
|
||||
return poly;
|
||||
};
|
||||
dc.DrawPolygon(3, tri);
|
||||
}
|
||||
|
||||
// Labels.
|
||||
// "Model Height" and "100%" share the same left x; the gap is larger than the
|
||||
// axis-arrow half-base so the text never visually touches the Y-axis arrow.
|
||||
const int label_left_x = y_axis_x + FromDIP(10);
|
||||
dc.SetTextForeground(label_muted);
|
||||
dc.DrawText(axis_y_title, label_left_x, y_title_y);
|
||||
|
||||
dc.SetFont(strong_font);
|
||||
dc.SetTextForeground(label_strong);
|
||||
dc.DrawText(pct_text, label_left_x, y_title_y + y_title_sz.y + y_title_pct_gap);
|
||||
|
||||
// Bottom-right "100%" sits under the right end of the plot; "Material Ratio" follows the
|
||||
// X-axis arrow tip (placement was already clamped above to leave room).
|
||||
dc.DrawText(pct_text, rc.x + rc.width - pct_text_sz.x, x_axis_y);
|
||||
dc.SetFont(label_font);
|
||||
dc.SetTextForeground(label_muted);
|
||||
dc.DrawText(axis_x_title, x_title_x, x_axis_y - x_title_sz.y / 2);
|
||||
|
||||
if (m_points.size() < 2 || !gc)
|
||||
return;
|
||||
|
||||
auto color_for_curve = [&](int curve_idx) -> wxColour {
|
||||
wxColour c = (curve_idx == 0) ? m_color_low : m_color_high;
|
||||
// Transparent filaments (alpha == 0, e.g. #FFFFFF00) would be invisible.
|
||||
// Lift alpha so the curve stays visible while still hinting at transparency.
|
||||
if (c.Alpha() == 0)
|
||||
c.Set(c.Red(), c.Green(), c.Blue(), 150);
|
||||
return c;
|
||||
};
|
||||
|
||||
auto build_polyline = [&](int curve_idx) -> std::vector<wxPoint2DDouble> {
|
||||
const int samples = std::max(128, rc.width * 2);
|
||||
std::vector<wxPoint2DDouble> poly;
|
||||
poly.reserve(samples + 1);
|
||||
for (int s = 0; s <= samples; ++s) {
|
||||
const double x = double(s) / samples;
|
||||
const double y0 = sample_curve_y(x);
|
||||
const double vy = to_visual_y(curve_idx, y0);
|
||||
poly.push_back(data_to_px_f(x, vy));
|
||||
// Draw unselected first so the selected curve sits on top.
|
||||
const int other = 1 - m_selected_curve;
|
||||
for (const int idx : {other, m_selected_curve}) {
|
||||
std::vector<wxPoint2DDouble> pts = build_polyline(idx);
|
||||
if (pts.empty())
|
||||
continue;
|
||||
curves.push_back({std::move(pts), color_for_curve(idx), idx == m_selected_curve ? kStrokeSelected : kStrokeUnselected});
|
||||
}
|
||||
return poly;
|
||||
};
|
||||
|
||||
// Only the geometry goes through the graphics context: dc.DrawLines() takes integer wxPoint
|
||||
// and would quantize the curve back to whole pixels. The pen is still set on the dc, which
|
||||
// forwards it here while keeping its own cached state in sync for later dc drawing.
|
||||
auto draw_polyline = [&](const std::vector<wxPoint2DDouble>& poly, const wxColour& col, int stroke_dip) {
|
||||
dc.SetPen(wxPen(col, FromDIP(stroke_dip)));
|
||||
gc->StrokeLines(poly.size(), poly.data());
|
||||
};
|
||||
|
||||
// Outline only when the curve color is perceptually close to the background; otherwise
|
||||
// the plain filament color reads fine and the extra stroke would look heavy.
|
||||
auto needs_outline = [&](const wxColour& c) {
|
||||
return calc_color_distance(c, bg) < kBgSimilarThreshold;
|
||||
};
|
||||
|
||||
auto draw_one = [&](int curve_idx, int stroke_dip) {
|
||||
const auto poly = build_polyline(curve_idx);
|
||||
const wxColour col = color_for_curve(curve_idx);
|
||||
if (needs_outline(col))
|
||||
draw_polyline(poly, outline_color, stroke_dip + kOutlineExtraDip);
|
||||
draw_polyline(poly, col, stroke_dip);
|
||||
};
|
||||
|
||||
// Draw unselected first so the selected curve sits on top.
|
||||
const int other = 1 - m_selected_curve;
|
||||
draw_one(other, kStrokeUnselected);
|
||||
draw_one(m_selected_curve, kStrokeSelected);
|
||||
|
||||
// Control points (selected curve only): hollow circle with axis-color border, theme-aware fill.
|
||||
// Drawn on the graphics context with a sub-pixel center so the ring stays centered on the
|
||||
// curve instead of drifting up to half a pixel off it; pen and brush go through the dc for
|
||||
// the same reason as in draw_polyline above.
|
||||
const double r = FromDIP(kPointRadius);
|
||||
dc.SetPen(wxPen(axis_color, 1));
|
||||
dc.SetBrush(wxBrush(point_fill));
|
||||
for (size_t i = 0; i < m_points.size(); ++i) {
|
||||
const double vy = to_visual_y(m_selected_curve, m_points[i].y);
|
||||
const wxPoint2DDouble p = data_to_px_f(m_points[i].x, vy);
|
||||
gc->DrawEllipse(p.m_x - r, p.m_y - r, r * 2, r * 2);
|
||||
// Control points (selected curve only).
|
||||
anchors.reserve(m_points.size());
|
||||
for (size_t i = 0; i < m_points.size(); ++i) {
|
||||
const double vy = to_visual_y(m_selected_curve, m_points[i].y);
|
||||
anchors.push_back(data_to_px_f(m_points[i].x, vy));
|
||||
}
|
||||
}
|
||||
|
||||
const MixedGradientTheme theme{bg, grid_color, axis_color, label_muted, label_strong, outline_color, point_fill};
|
||||
draw_mixed_gradient_plot(raw_dc, GetClientSize(), curves, anchors, theme);
|
||||
}
|
||||
|
||||
void GradientCurveEditor::on_left_down(wxMouseEvent& evt)
|
||||
|
||||
@@ -790,7 +790,7 @@ void IMSlider::draw_ticks(const ImRect& slideable_region) {
|
||||
|
||||
void IMSlider::show_tooltip(const std::string tooltip) {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 6 * m_scale, 3 * m_scale });
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, { 3 * m_scale });
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 3 * m_scale);
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, { 0,0,0,0 });
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.00f, 1.00f, 1.00f, 1.00f));
|
||||
|
||||
@@ -174,6 +174,7 @@ void KBShortcutsDialog::fill_shortcuts()
|
||||
{ ctrl + "O", L("Open Project") },
|
||||
{ ctrl + "S", L("Save Project") },
|
||||
{ ctrl + shift + "S", L("Save Project as")},
|
||||
{ ctrl + shift + "E", L("Publish 3MF") },
|
||||
// File>Import
|
||||
{ ctrl + "I", L("Import geometry data from STL/STEP/3MF/OBJ/AMF files") },
|
||||
// File>Export
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
#include "Plater.hpp"
|
||||
#include "WebViewDialog.hpp"
|
||||
#include "../Utils/Process.hpp"
|
||||
#include "format.hpp"
|
||||
// BBS
|
||||
#include "PartPlate.hpp"
|
||||
#include "Preferences.hpp"
|
||||
@@ -49,11 +48,9 @@
|
||||
#include "../Utils/NetworkAgentFactory.hpp"
|
||||
#include "../Utils/PrintHost.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <string_view>
|
||||
|
||||
#include "GUI_App.hpp"
|
||||
#include "UnsavedChangesDialog.hpp"
|
||||
#include "PublishSettingsDialog.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "Notebook.hpp"
|
||||
#include "GUI_Factories.hpp"
|
||||
@@ -743,6 +740,10 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
||||
if (m_plater) { m_plater->add_file(); }
|
||||
return;
|
||||
}
|
||||
if (evt.CmdDown() && evt.ShiftDown() && evt.GetKeyCode() == 'E') {
|
||||
if (can_export_model()) publish_project();
|
||||
return;
|
||||
}
|
||||
evt.Skip();
|
||||
});
|
||||
|
||||
@@ -1590,7 +1591,7 @@ void MainFrame::register_win32_callbacks()
|
||||
//static GUID GUID_DEVINTERFACE_USB_DEVICE = { 0xA5DCBF10, 0x6530, 0x11D2, 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED };
|
||||
//static GUID GUID_DEVINTERFACE_DISK = { 0x53f56307, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b };
|
||||
//static GUID GUID_DEVINTERFACE_VOLUME = { 0x71a27cdd, 0x812a, 0x11d0, 0xbe, 0xc7, 0x08, 0x00, 0x2b, 0xe2, 0x09, 0x2f };
|
||||
static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 };
|
||||
static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, { 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } };
|
||||
|
||||
// Register USB HID (Human Interface Devices) notifications to trigger the 3DConnexion enumeration.
|
||||
DEV_BROADCAST_DEVICEINTERFACE NotificationFilter = { 0 };
|
||||
@@ -1630,7 +1631,7 @@ void MainFrame::register_win32_callbacks()
|
||||
|
||||
{
|
||||
static constexpr int device_count = 1;
|
||||
RAWINPUTDEVICE devices[device_count] = { 0 };
|
||||
RAWINPUTDEVICE devices[device_count] = {};
|
||||
// multi-axis mouse (SpaceNavigator, etc.)
|
||||
devices[0].usUsagePage = 0x01;
|
||||
devices[0].usUsage = 0x08;
|
||||
@@ -1739,6 +1740,22 @@ bool MainFrame::save_project_as(const wxString& filename)
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MainFrame::publish_project()
|
||||
{
|
||||
if (m_plater == nullptr)
|
||||
return;
|
||||
// Seed the dialog from the session selection (a remembered state or a freshly loaded
|
||||
// published 3MF); a null pointer means "fresh", keeping the dirty defaults.
|
||||
std::vector<std::string> pending_keys;
|
||||
std::vector<Slic3r::PublishedMaterialEntry> pending_material;
|
||||
const bool has_prior = m_plater->get_pending_published(pending_keys, pending_material);
|
||||
PublishSettingsDialog dlg(this, has_prior ? &pending_keys : nullptr, has_prior ? &pending_material : nullptr);
|
||||
if (dlg.ShowModal() != wxID_OK)
|
||||
return;
|
||||
m_plater->set_pending_published(dlg.GetPublishedKeys(), dlg.GetPublishedMaterialKeys());
|
||||
m_plater->export_published_3mf(dlg.GetPublishedKeys(), dlg.GetPublishedMaterialKeys());
|
||||
}
|
||||
|
||||
bool MainFrame::can_upload() const
|
||||
{
|
||||
return true;
|
||||
@@ -2834,6 +2851,20 @@ void MainFrame::init_menubar_as_editor()
|
||||
[this](){return m_plater != nullptr && can_save_as(); }, this);
|
||||
#endif
|
||||
|
||||
// BBS: publish
|
||||
fileMenu->AppendSeparator();
|
||||
auto publish_handler = [this](wxCommandEvent&) { publish_project(); };
|
||||
|
||||
#ifndef __APPLE__
|
||||
append_menu_item(fileMenu, wxID_ANY, _L("Publish 3MF") + dots + "\t" + ctrl + shift + "E", _L("Export a 3MF file with the selected settings embedded"),
|
||||
publish_handler, "menu_publish", nullptr,
|
||||
[this](){return can_export_model(); }, this);
|
||||
#else
|
||||
append_menu_item(fileMenu, wxID_ANY, _L("Publish 3MF") + dots + "\t" + ctrl + shift + "E", _L("Export a 3MF file with the selected settings embedded"),
|
||||
publish_handler, "", nullptr,
|
||||
[this](){return can_export_model(); }, this);
|
||||
#endif
|
||||
|
||||
|
||||
fileMenu->AppendSeparator();
|
||||
|
||||
@@ -4212,15 +4243,23 @@ std::wstring MainFrame::FileHistory::GetThumbnailUrl(int index) const
|
||||
return wss.str();
|
||||
}
|
||||
|
||||
bool MainFrame::FileHistory::GetPublished(int index) const
|
||||
{
|
||||
return index >= 0 && index < static_cast<int>(m_published_files.size()) && m_published_files[index];
|
||||
}
|
||||
|
||||
void MainFrame::FileHistory::AddFileToHistory(const wxString &file)
|
||||
{
|
||||
if (this->m_fileMaxFiles == 0)
|
||||
return;
|
||||
wxFileHistory::AddFileToHistory(file);
|
||||
if (m_load_called)
|
||||
if (m_load_called) {
|
||||
m_thumbnails.push_front(bbs_3mf_get_thumbnail(into_u8(file).c_str()));
|
||||
else
|
||||
m_published_files.push_front(bbs_3mf_is_published(into_u8(file)));
|
||||
} else {
|
||||
m_thumbnails.push_front("");
|
||||
m_published_files.push_front(false);
|
||||
}
|
||||
}
|
||||
|
||||
void MainFrame::FileHistory::RemoveFileFromHistory(size_t i)
|
||||
@@ -4229,6 +4268,7 @@ void MainFrame::FileHistory::RemoveFileFromHistory(size_t i)
|
||||
return;
|
||||
wxFileHistory::RemoveFileFromHistory(i);
|
||||
m_thumbnails.erase(m_thumbnails.begin() + i);
|
||||
m_published_files.erase(m_published_files.begin() + i);
|
||||
}
|
||||
|
||||
size_t MainFrame::FileHistory::FindFileInHistory(const wxString & file)
|
||||
@@ -4244,6 +4284,7 @@ void MainFrame::FileHistory::LoadThumbnails()
|
||||
if (!thumbnail.empty()) {
|
||||
m_thumbnails[i] = thumbnail;
|
||||
}
|
||||
m_published_files[i] = bbs_3mf_is_published(into_u8(GetHistoryFile(i)));
|
||||
}
|
||||
});
|
||||
m_load_called = true;
|
||||
@@ -4264,6 +4305,7 @@ void MainFrame::get_recent_projects(boost::property_tree::wptree &tree, int imag
|
||||
std::wstring proj = m_recent_projects.GetHistoryFile(i).ToStdWstring();
|
||||
item.put(L"project_name", proj.substr(proj.find_last_of(L"/\\") + 1));
|
||||
item.put(L"path", proj);
|
||||
item.put(L"published", m_recent_projects.GetPublished(i) ? L"1" : L"0");
|
||||
boost::system::error_code ec;
|
||||
std::time_t t = boost::filesystem::last_write_time(proj, ec);
|
||||
if (!ec) {
|
||||
|
||||
@@ -178,6 +178,7 @@ class MainFrame : public DPIFrame
|
||||
{
|
||||
FileHistory(int max) : wxFileHistory(max) {}
|
||||
std::wstring GetThumbnailUrl(int index) const;
|
||||
bool GetPublished(int index) const;
|
||||
|
||||
virtual void AddFileToHistory(const wxString &file);
|
||||
virtual void RemoveFileFromHistory(size_t i);
|
||||
@@ -188,6 +189,7 @@ class MainFrame : public DPIFrame
|
||||
void SetMaxFiles(int max);
|
||||
private:
|
||||
std::deque<std::string> m_thumbnails;
|
||||
std::deque<bool> m_published_files; // parallel to m_thumbnails: is it a published 3mf?
|
||||
bool m_load_called = false;
|
||||
};
|
||||
|
||||
@@ -341,6 +343,8 @@ public:
|
||||
bool can_upload() const;
|
||||
void save_project();
|
||||
bool save_project_as(const wxString& filename = wxString());
|
||||
// Open the Publish dialog and export the selected settings as a published 3MF.
|
||||
void publish_project();
|
||||
|
||||
void add_to_recent_projects(const wxString& filename);
|
||||
void get_recent_projects(boost::property_tree::wptree &tree, int images);
|
||||
|
||||
@@ -845,23 +845,8 @@ wxBoxSizer* MixedFilamentDialog::create_ratio_slider()
|
||||
m_ratio_bar->Bind(wxEVT_PAINT, [this](wxPaintEvent&) {
|
||||
wxBufferedPaintDC dc(m_ratio_bar);
|
||||
wxSize sz = m_ratio_bar->GetClientSize();
|
||||
|
||||
wxColour col_a = comp_colour(0), col_b = comp_colour(1);
|
||||
|
||||
for (int x = 0; x < sz.GetWidth(); ++x) {
|
||||
double t = (double)x / sz.GetWidth();
|
||||
wxColour c = blend_colors(col_a, col_b, 1.0 - t);
|
||||
dc.SetPen(wxPen(c));
|
||||
dc.DrawLine(x, 0, x, sz.GetHeight());
|
||||
}
|
||||
|
||||
int div_x = (int)(ratio(1) / 100.0 * sz.GetWidth());
|
||||
// Fixed in both themes, like the triangle picker's drag handle: the divider is drawn over
|
||||
// blended filament colour, so it has to keep its contrast against data rather than chrome.
|
||||
dc.SetPen(wxPen(wxColour(80, 80, 80), FromDIP(4)));
|
||||
dc.DrawLine(div_x, 0, div_x, sz.GetHeight());
|
||||
dc.SetPen(wxPen(*wxWHITE, FromDIP(2)));
|
||||
dc.DrawLine(div_x, 0, div_x, sz.GetHeight());
|
||||
draw_mixed_ratio_blend_bar(dc, wxRect(0, 0, sz.GetWidth(), sz.GetHeight()),
|
||||
comp_colour(0), comp_colour(1), ratio(1) / 100.0);
|
||||
});
|
||||
|
||||
m_ratio_bar->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& e) {
|
||||
@@ -917,52 +902,8 @@ wxBoxSizer* MixedFilamentDialog::create_ratio_slider()
|
||||
}
|
||||
|
||||
// ---- Triangle (ternary) ratio picker ----
|
||||
|
||||
// Barycentric coordinate utilities
|
||||
struct TriPoint { double x, y; };
|
||||
|
||||
static double tri_signed_area2(TriPoint a, TriPoint b, TriPoint c)
|
||||
{
|
||||
return (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y);
|
||||
}
|
||||
|
||||
static bool tri_contains(TriPoint p, TriPoint v0, TriPoint v1, TriPoint v2)
|
||||
{
|
||||
double total = tri_signed_area2(v0, v1, v2);
|
||||
if (std::abs(total) < 1e-9) return false;
|
||||
double s0 = tri_signed_area2(p, v1, v2) / total;
|
||||
double s1 = tri_signed_area2(v0, p, v2) / total;
|
||||
double s2 = 1.0 - s0 - s1;
|
||||
return s0 >= -0.001 && s1 >= -0.001 && s2 >= -0.001;
|
||||
}
|
||||
|
||||
static void tri_barycentric(TriPoint p, TriPoint v0, TriPoint v1, TriPoint v2,
|
||||
double& w0, double& w1, double& w2)
|
||||
{
|
||||
double total = std::abs(tri_signed_area2(v0, v1, v2));
|
||||
if (total < 1e-9) { w0 = w1 = w2 = 1.0 / 3.0; return; }
|
||||
w0 = std::abs(tri_signed_area2(p, v1, v2)) / total;
|
||||
w1 = std::abs(tri_signed_area2(v0, p, v2)) / total;
|
||||
w2 = 1.0 - w0 - w1;
|
||||
w0 = std::clamp(w0, 0.0, 1.0);
|
||||
w1 = std::clamp(w1, 0.0, 1.0);
|
||||
w2 = std::clamp(w2, 0.0, 1.0);
|
||||
double s = w0 + w1 + w2;
|
||||
if (s > 0) { w0 /= s; w1 /= s; w2 /= s; }
|
||||
}
|
||||
|
||||
static TriPoint tri_clamp(TriPoint p, TriPoint v0, TriPoint v1, TriPoint v2)
|
||||
{
|
||||
double w0, w1, w2;
|
||||
tri_barycentric(p, v0, v1, v2, w0, w1, w2);
|
||||
w0 = std::clamp(w0, 0.0, 1.0);
|
||||
w1 = std::clamp(w1, 0.0, 1.0);
|
||||
w2 = std::clamp(w2, 0.0, 1.0);
|
||||
double s = w0 + w1 + w2;
|
||||
if (s > 0) { w0 /= s; w1 /= s; w2 /= s; }
|
||||
return {w0 * v0.x + w1 * v1.x + w2 * v2.x,
|
||||
w0 * v0.y + w1 * v1.y + w2 * v2.y};
|
||||
}
|
||||
// The barycentric utilities (TriPoint, tri_contains, tri_barycentric, tri_clamp) live in
|
||||
// FilamentBitmapUtils so the Publish dialog can mirror this picker read-only.
|
||||
|
||||
wxBoxSizer* MixedFilamentDialog::create_triangle_picker()
|
||||
{
|
||||
@@ -996,72 +937,15 @@ wxBoxSizer* MixedFilamentDialog::create_triangle_picker()
|
||||
wxSize sz = m_triangle_panel->GetClientSize();
|
||||
auto [v0, v1, v2] = get_vertices();
|
||||
|
||||
wxColour tri_bg = StateColor::darkModeColorFor(*wxWHITE);
|
||||
dc.SetBrush(wxBrush(tri_bg));
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
dc.DrawRectangle(0, 0, sz.GetWidth(), sz.GetHeight());
|
||||
|
||||
wxColour c0 = comp_colour(0), c1 = comp_colour(1), c2 = comp_colour(2);
|
||||
|
||||
const bool cache_valid = m_tri_cache_bmp.IsOk() &&
|
||||
m_tri_cache_size == sz &&
|
||||
m_tri_cache_c0 == c0 && m_tri_cache_c1 == c1 && m_tri_cache_c2 == c2;
|
||||
|
||||
if (!cache_valid) {
|
||||
int min_y = (int)std::min({v0.y, v1.y, v2.y});
|
||||
int max_y = (int)std::max({v0.y, v1.y, v2.y});
|
||||
int min_x = (int)std::min({v0.x, v1.x, v2.x});
|
||||
int max_x = (int)std::max({v0.x, v1.x, v2.x});
|
||||
|
||||
m_tri_cache_bmp = wxBitmap(sz.GetWidth(), sz.GetHeight(), 24);
|
||||
wxMemoryDC mdc(m_tri_cache_bmp);
|
||||
mdc.SetBrush(wxBrush(tri_bg));
|
||||
mdc.SetPen(*wxTRANSPARENT_PEN);
|
||||
mdc.DrawRectangle(0, 0, sz.GetWidth(), sz.GetHeight());
|
||||
|
||||
for (int py = min_y; py <= max_y; ++py) {
|
||||
for (int px = min_x; px <= max_x; ++px) {
|
||||
TriPoint p = {(double)px, (double)py};
|
||||
if (!tri_contains(p, v0, v1, v2)) continue;
|
||||
double w0, w1, w2;
|
||||
tri_barycentric(p, v0, v1, v2, w0, w1, w2);
|
||||
unsigned char mr, mg, mb;
|
||||
if (w0 + w1 > 1e-6) {
|
||||
float t01 = static_cast<float>(w1 / (w0 + w1));
|
||||
Slic3r::filament_mixer_lerp(c0.Red(), c0.Green(), c0.Blue(),
|
||||
c1.Red(), c1.Green(), c1.Blue(),
|
||||
t01, &mr, &mg, &mb);
|
||||
float t2 = static_cast<float>(w2);
|
||||
Slic3r::filament_mixer_lerp(mr, mg, mb,
|
||||
c2.Red(), c2.Green(), c2.Blue(),
|
||||
t2, &mr, &mg, &mb);
|
||||
} else {
|
||||
mr = c2.Red(); mg = c2.Green(); mb = c2.Blue();
|
||||
}
|
||||
mdc.SetPen(wxPen(wxColour(mr, mg, mb)));
|
||||
mdc.DrawPoint(px, py);
|
||||
}
|
||||
}
|
||||
|
||||
mdc.SetPen(wxPen(StateColor::darkModeColorFor(wxColour("#CECECE")), 1));
|
||||
mdc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
wxPoint pts[3] = {{(int)v0.x, (int)v0.y}, {(int)v1.x, (int)v1.y}, {(int)v2.x, (int)v2.y}};
|
||||
mdc.DrawPolygon(3, pts);
|
||||
|
||||
mdc.SelectObject(wxNullBitmap);
|
||||
m_tri_cache_c0 = c0; m_tri_cache_c1 = c1; m_tri_cache_c2 = c2;
|
||||
m_tri_cache_size = sz;
|
||||
}
|
||||
|
||||
dc.DrawBitmap(m_tri_cache_bmp, 0, 0);
|
||||
|
||||
// Drag handle (always redrawn on top of cached bitmap)
|
||||
double hx = m_tri_wx * v0.x + m_tri_wy * v1.x + m_tri_wz * v2.x;
|
||||
double hy = m_tri_wx * v0.y + m_tri_wy * v1.y + m_tri_wz * v2.y;
|
||||
int handle_r = FromDIP(5);
|
||||
dc.SetBrush(*wxWHITE_BRUSH);
|
||||
dc.SetPen(wxPen(wxColour("#262E30"), FromDIP(2)));
|
||||
dc.DrawCircle((int)hx, (int)hy, handle_r);
|
||||
// Draw the background, cached barycentric fill, outline and drag-handle marker through the
|
||||
// shared picker painter (same geometry the read-only Publish preview uses).
|
||||
draw_mixed_triangle_picker(dc, sz,
|
||||
{comp_colour(0), comp_colour(1), comp_colour(2)},
|
||||
{m_tri_wx, m_tri_wy, m_tri_wz},
|
||||
{StateColor::darkModeColorFor(*wxWHITE),
|
||||
StateColor::darkModeColorFor(wxColour("#CECECE")),
|
||||
StateColor::darkModeColorFor(wxColour("#262E30")),
|
||||
StateColor::darkModeColorFor(COLOR_LABEL_MUTED)});
|
||||
|
||||
if (m_result.ratios.size() >= 3) {
|
||||
dc.SetFont(::Label::Body_10);
|
||||
|
||||
@@ -169,10 +169,6 @@ private:
|
||||
// Triangle picker drag point (barycentric weights)
|
||||
double m_tri_wx{0.333}, m_tri_wy{0.333}, m_tri_wz{0.334};
|
||||
|
||||
// Cached triangle color bitmap (invalidated when colors or size change)
|
||||
wxBitmap m_tri_cache_bmp;
|
||||
wxColour m_tri_cache_c0, m_tri_cache_c1, m_tri_cache_c2;
|
||||
wxSize m_tri_cache_size;
|
||||
std::array<RatioLabelPanel*, 3> m_triangle_ratio_labels{nullptr, nullptr, nullptr};
|
||||
};
|
||||
|
||||
|
||||
@@ -3083,7 +3083,7 @@ bool NotificationManager::push_notification_data(std::unique_ptr<NotificationMan
|
||||
}
|
||||
bool retval = false;
|
||||
if (this->activate_existing(notification.get())) {
|
||||
if (m_initialized) { // ignore update action - it cant be initialized if canvas and imgui context is not ready
|
||||
if (m_initialized && m_imgui_ready) {
|
||||
if (notification->get_type() == NotificationType::SlicingWarning) {
|
||||
m_pop_notifications.back()->append(notification->get_data().ori_text);
|
||||
} else {
|
||||
@@ -3129,6 +3129,10 @@ void NotificationManager::stop_delayed_notifications_of_type(const NotificationT
|
||||
|
||||
void NotificationManager::render_notifications(GLCanvas3D &canvas, float overlay_width, float bottom_margin, float right_margin)
|
||||
{
|
||||
// Notifications render inside an ImGui frame, so the font atlas is built from this point on
|
||||
// and pushed notifications may safely measure their text.
|
||||
m_imgui_ready = true;
|
||||
|
||||
sort_notifications();
|
||||
|
||||
float bottom_up_last_y = bottom_margin; // ORCA dont scale margins
|
||||
@@ -3339,17 +3343,7 @@ size_t NotificationManager::get_notification_count() const
|
||||
void NotificationManager::bbl_show_plateinfo_notification(const std::string &text)
|
||||
{
|
||||
NotificationData data{NotificationType::BBLPlateInfo, NotificationLevel::PrintInfoNotificationLevel, BBL_NOTICE_MAX_INTERVAL, text};
|
||||
|
||||
for (std::unique_ptr<PopNotification> ¬ification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::BBLPlateInfo) {
|
||||
notification->reinit();
|
||||
notification->update(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto notification = std::make_unique<NotificationManager::PopNotification>(data, m_id_provider, m_evt_handler);
|
||||
push_notification_data(std::move(notification), 0);
|
||||
push_notification_data(data, 0);
|
||||
}
|
||||
|
||||
void NotificationManager::bbl_close_3mf_warn_notification()
|
||||
@@ -3360,20 +3354,10 @@ void NotificationManager::bbl_close_3mf_warn_notification()
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationManager::bbl_show_3mf_warn_notification(const std::string &text)
|
||||
void NotificationManager::bbl_show_3mf_warn_notification(const std::string &text, NotificationLevel level)
|
||||
{
|
||||
NotificationData data{NotificationType::BBL3MFInfo, NotificationLevel::ErrorNotificationLevel, BBL_NOTICE_MAX_INTERVAL, text};
|
||||
|
||||
for (std::unique_ptr<PopNotification> ¬ification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::BBL3MFInfo) {
|
||||
notification->reinit();
|
||||
notification->update(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto notification = std::make_unique<NotificationManager::PopNotification>(data, m_id_provider, m_evt_handler);
|
||||
push_notification_data(std::move(notification), 0);
|
||||
NotificationData data{NotificationType::BBL3MFInfo, level, BBL_NOTICE_MAX_INTERVAL, text};
|
||||
push_notification_data(data, 0);
|
||||
}
|
||||
|
||||
void NotificationManager::bbl_close_plateinfo_notification()
|
||||
@@ -3388,17 +3372,7 @@ void NotificationManager::bbl_close_plateinfo_notification()
|
||||
void NotificationManager::bbl_show_preview_only_notification(const std::string &text)
|
||||
{
|
||||
NotificationData data{NotificationType::BBLPreviewOnlyMode, NotificationLevel::WarningNotificationLevel, 0, text};
|
||||
|
||||
for (std::unique_ptr<PopNotification> ¬ification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::BBLPreviewOnlyMode) {
|
||||
notification->reinit();
|
||||
notification->update(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto notification = std::make_unique<NotificationManager::PopNotification>(data, m_id_provider, m_evt_handler);
|
||||
push_notification_data(std::move(notification), 0);
|
||||
push_notification_data(data, 0);
|
||||
}
|
||||
|
||||
void NotificationManager::bbl_close_preview_only_notification()
|
||||
|
||||
@@ -378,7 +378,9 @@ public:
|
||||
void bbl_close_plateinfo_notification();
|
||||
|
||||
//BBS-- 3mf warning
|
||||
void bbl_show_3mf_warn_notification(const std::string &text);
|
||||
// level defaults to the historical error styling; callers reporting informational
|
||||
// 3MF load notices (published settings) pass WarningNotificationLevel instead.
|
||||
void bbl_show_3mf_warn_notification(const std::string &text, NotificationLevel level = NotificationLevel::ErrorNotificationLevel);
|
||||
void bbl_close_3mf_warn_notification();
|
||||
|
||||
//BBS--preview only mode
|
||||
@@ -1052,6 +1054,11 @@ private:
|
||||
bool m_is_dark = false;
|
||||
// set by init(), until false notifications are only added not updated and frame is not requested after push
|
||||
bool m_initialized{ false };
|
||||
// set by render_notifications() on the first rendered frame. m_initialized only proves the
|
||||
// manager exists, not that the ImGui context can measure text: the font atlas is built lazily
|
||||
// in ImGuiWrapper::new_frame() on the first GL render, so updating a notification before that
|
||||
// (PopNotification::init -> count_spaces -> ImGui::CalcTextSize) dereferences a null font.
|
||||
bool m_imgui_ready{ false };
|
||||
// Target for wxWidgets events sent by clicking on the hyperlink available at some notifications.
|
||||
wxEvtHandler* m_evt_handler;
|
||||
// Cache of IDs to identify and reuse ImGUI windows.
|
||||
@@ -1076,7 +1083,10 @@ private:
|
||||
NotificationType::ProgressBar,
|
||||
NotificationType::PrintHostUpload,
|
||||
NotificationType::SimplifySuggestion,
|
||||
NotificationType::ValidateWarning
|
||||
NotificationType::ValidateWarning,
|
||||
// A published file load can produce several distinct 3MF warnings (invalid values,
|
||||
// skipped settings, changed slots); let them stack rather than clobber each other.
|
||||
NotificationType::BBL3MFInfo
|
||||
};
|
||||
//prepared (basic) notifications
|
||||
// non-static so its not loaded too early. If static, the translations wont load correctly.
|
||||
|
||||
@@ -66,7 +66,6 @@ class ParamsPanel : public wxPanel
|
||||
{
|
||||
#if __WXOSX__
|
||||
wxWindow* m_tmp_panel;
|
||||
int m_size_move = -1;
|
||||
#endif // __WXOSX__
|
||||
|
||||
private:
|
||||
|
||||
@@ -1112,7 +1112,7 @@ void PartPlate::show_tooltip(const std::string tooltip)
|
||||
{
|
||||
const auto scale = m_plater->get_current_canvas3D()->get_scale();
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {6 * scale, 3 * scale});
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, {3 * scale});
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 3 * scale);
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, {0, 0, 0, 0});
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.00f, 1.00f, 1.00f, 1.00f));
|
||||
|
||||
+399
-38
@@ -1,12 +1,10 @@
|
||||
#include "Plater.hpp"
|
||||
#include "../Utils/NetworkAgent.hpp"
|
||||
#include "../Utils/NetworkAgentFactory.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r_version.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <numeric>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
@@ -16,8 +14,6 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <regex>
|
||||
#include <future>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
@@ -69,7 +65,6 @@
|
||||
#include "libslic3r/Format/bbs_3mf.hpp"
|
||||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/SLA/Hollowing.hpp"
|
||||
#include "libslic3r/SLA/SupportPoint.hpp"
|
||||
#include "libslic3r/SLA/ReprojectPointsOnMesh.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
@@ -78,16 +73,15 @@
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/PublishSettings.hpp"
|
||||
#include "slic3r/Utils/CrealityPrint.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/ObjColorUtils.hpp"
|
||||
// For stl export
|
||||
#include "libslic3r/CSGMesh/ModelToCSGMesh.hpp"
|
||||
#include "libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp"
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "GuiColor.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
#ifdef __WXGTK__
|
||||
#include "LinuxDisplayBackend.hpp"
|
||||
@@ -123,7 +117,6 @@
|
||||
#include "SendMultiMachinePage.hpp"
|
||||
#include "SendToPrinter.hpp"
|
||||
#include "PublishDialog.hpp"
|
||||
#include "ModelMall.hpp"
|
||||
#include "ConfigWizard.hpp"
|
||||
#include "SyncAmsInfoDialog.hpp"
|
||||
#include "../Utils/ASCIIFolding.hpp"
|
||||
@@ -149,7 +142,6 @@
|
||||
#include "ParamsDialog.hpp"
|
||||
#include "ImageDPIFrame.hpp"
|
||||
#include "Widgets/Label.hpp"
|
||||
#include "Widgets/RoundedRectangle.hpp"
|
||||
#include "Widgets/RadioGroup.hpp"
|
||||
#include "Widgets/CheckBox.hpp"
|
||||
#include "Widgets/Button.hpp"
|
||||
@@ -6712,6 +6704,12 @@ struct Plater::priv
|
||||
SendToPrinterDialog* m_send_to_sdcard_dlg = nullptr;
|
||||
PublishDialog *m_publish_dlg = nullptr;
|
||||
|
||||
// Session-level stash of the last published selection. Written on publish and on
|
||||
// loading a published 3MF; read when the Publish dialog is opened.
|
||||
bool m_has_pending_published{false};
|
||||
std::vector<std::string> m_pending_published_keys;
|
||||
std::vector<Slic3r::PublishedMaterialEntry> m_pending_material_keys;
|
||||
|
||||
// Data
|
||||
Slic3r::DynamicPrintConfig *config; // FIXME: leak?
|
||||
Slic3r::Print fff_print;
|
||||
@@ -6936,7 +6934,10 @@ struct Plater::priv
|
||||
|
||||
// BBS: backup & restore
|
||||
using LoadProgressCallback = std::function<bool(int, const wxString&)>;
|
||||
std::vector<size_t> load_files(const std::vector<fs::path>& input_files, LoadStrategy strategy, bool ask_multi = false);
|
||||
std::vector<size_t> load_files(const std::vector<fs::path>& input_files,
|
||||
LoadStrategy strategy,
|
||||
bool ask_multi = false,
|
||||
bool* published_out = nullptr);
|
||||
std::vector<size_t> load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false, bool split_object = false, bool auto_drop = true);
|
||||
|
||||
// Texture-to-color import: a mesh loaded with UVs + a texture map gets its faces clustered
|
||||
@@ -6964,7 +6965,7 @@ struct Plater::priv
|
||||
std::function<bool()> cancel_callback = {});
|
||||
|
||||
fs::path get_export_file_path(GUI::FileType file_type);
|
||||
wxString get_export_file(GUI::FileType file_type);
|
||||
wxString get_export_file(GUI::FileType file_type, const wxString& title = {}, bool published = false);
|
||||
|
||||
// BBS
|
||||
void load_auxiliary_files();
|
||||
@@ -8272,7 +8273,10 @@ void read_binary_stl(const std::string& filename, std::string& model_id, std::st
|
||||
}
|
||||
|
||||
// BBS: backup & restore
|
||||
std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_files, LoadStrategy strategy, bool ask_multi)
|
||||
std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_files,
|
||||
LoadStrategy strategy,
|
||||
bool ask_multi,
|
||||
bool* published_out)
|
||||
{
|
||||
std::vector<size_t> empty_result;
|
||||
bool dlg_cont = true;
|
||||
@@ -8352,6 +8356,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
const float INPUT_FILES_RATIO = 0.7;
|
||||
const float INIT_MODEL_RATIO = 0.75;
|
||||
const float CENTER_AROUND_ORIGIN_RATIO = 0.8;
|
||||
|
||||
const float LOAD_MODEL_RATIO = 0.9;
|
||||
|
||||
for (size_t i = 0; i < input_files.size(); ++i) {
|
||||
@@ -8392,6 +8397,11 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
DynamicPrintConfig config;
|
||||
Semver file_version;
|
||||
En3mfType en_3mf_file_type = En3mfType::From_BBS;
|
||||
// BBS: a "published" 3MF carries a flag plus the author-selected setting keys;
|
||||
// on load keep the user's current presets and overlay only those keys. Declared
|
||||
// here (outside the config block below) so it stays alive for the embedded-preset
|
||||
// gate, the metadata strip and the preset overlay after the block closes.
|
||||
PublishedConfig published_config;
|
||||
{
|
||||
DynamicPrintConfig config_loaded;
|
||||
|
||||
@@ -8419,6 +8429,107 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
<< boost::format(", plate_data.size %1%, project_preset.size %2%, is_bbs_or_orca_3mf %3%, file_version %4% \n") % plate_data.size() %
|
||||
project_presets.size() % (en_3mf_file_type == En3mfType::From_BBS || en_3mf_file_type == En3mfType::From_Orca) % file_version.to_string();
|
||||
|
||||
// BBS: a "published" 3MF carries a flag plus the author-selected setting keys;
|
||||
// on load keep the user's current presets and overlay only those keys. Parsed
|
||||
// here (before the version/fallback chain below) because a published file has
|
||||
// no project_settings.config: its values travel in the published_config
|
||||
// metadata payload, which must fill config_loaded before the chain decides
|
||||
// whether to import geometry only.
|
||||
if (model.model_info != nullptr) {
|
||||
auto published_it = model.model_info->metadata_items.find(ORCA_PUBLISHED_TAG);
|
||||
if (published_it != model.model_info->metadata_items.end() && is_published_3mf_flag(published_it->second)) {
|
||||
published_config.published = true;
|
||||
auto keys_it = model.model_info->metadata_items.find(ORCA_PUBLISHED_KEYS_TAG);
|
||||
if (keys_it != model.model_info->metadata_items.end()) {
|
||||
try {
|
||||
auto j = nlohmann::json::parse(keys_it->second);
|
||||
if (j.is_array())
|
||||
for (const auto& k : j)
|
||||
if (k.is_string())
|
||||
published_config.published_keys.emplace_back(k.get<std::string>());
|
||||
} catch (...) {
|
||||
// Ignore malformed published_keys; the project still loads normally.
|
||||
}
|
||||
}
|
||||
|
||||
auto material_keys_it = model.model_info->metadata_items.find(ORCA_PUBLISHED_MATERIAL_TAG);
|
||||
if (material_keys_it != model.model_info->metadata_items.end()) {
|
||||
try {
|
||||
auto jm = nlohmann::json::parse(material_keys_it->second);
|
||||
if (jm.is_array())
|
||||
for (const auto& m : jm) {
|
||||
try {
|
||||
// Malformed entries are isolated so one bad item
|
||||
// cannot discard valid entries that follow it.
|
||||
if (!m.is_object())
|
||||
continue;
|
||||
PublishedMaterialEntry entry;
|
||||
const auto mat_it = m.find("material");
|
||||
if (mat_it != m.end() && mat_it->is_object()) {
|
||||
const auto& mat = *mat_it;
|
||||
if (mat.contains("filament_type") && mat["filament_type"].is_string())
|
||||
entry.filament_type = mat["filament_type"].get<std::string>();
|
||||
if (mat.contains("filament_vendor") && mat["filament_vendor"].is_string())
|
||||
entry.filament_vendor = mat["filament_vendor"].get<std::string>();
|
||||
if (mat.contains("filament_id") && mat["filament_id"].is_string())
|
||||
entry.filament_id = mat["filament_id"].get<std::string>();
|
||||
if (mat.contains("setting_id") && mat["setting_id"].is_string())
|
||||
entry.setting_id = mat["setting_id"].get<std::string>();
|
||||
if (mat.contains("name") && mat["name"].is_string())
|
||||
entry.preset_name = mat["name"].get<std::string>();
|
||||
}
|
||||
if (m.contains("slot") && m["slot"].is_number_integer())
|
||||
entry.slot = m["slot"].get<int>();
|
||||
const auto entry_keys_it = m.find("keys");
|
||||
if (entry_keys_it != m.end() && entry_keys_it->is_array())
|
||||
for (const auto& k : *entry_keys_it)
|
||||
if (k.is_string())
|
||||
entry.keys.emplace_back(k.get<std::string>());
|
||||
// Fields always written by the current exporter.
|
||||
if (m.contains("full") && m["full"].is_boolean())
|
||||
entry.full = m["full"].get<bool>();
|
||||
const auto entry_full_keys_it = m.find("full_keys");
|
||||
if (entry_full_keys_it != m.end() && entry_full_keys_it->is_array())
|
||||
for (const auto& k : *entry_full_keys_it)
|
||||
if (k.is_string())
|
||||
entry.full_keys.emplace_back(k.get<std::string>());
|
||||
if (m.contains("publish_type") && m["publish_type"].is_boolean())
|
||||
entry.publish_type = m["publish_type"].get<bool>();
|
||||
if (m.contains("type") && m["type"].is_string())
|
||||
entry.publish_type_value = m["type"].get<std::string>();
|
||||
if (m.contains("publish_color") && m["publish_color"].is_boolean())
|
||||
entry.publish_color = m["publish_color"].get<bool>();
|
||||
if (m.contains("color") && m["color"].is_string())
|
||||
entry.color = m["color"].get<std::string>();
|
||||
published_config.material_keys.emplace_back(std::move(entry));
|
||||
} catch (const nlohmann::json::exception&) {
|
||||
// Ignore only this malformed material entry.
|
||||
}
|
||||
}
|
||||
} catch (const nlohmann::json::exception&) {
|
||||
// Ignore malformed published_material_keys; the project still loads normally.
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild the published values from the metadata payload: a published
|
||||
// file carries no project_settings.config, so config_loaded is filled
|
||||
// from here; a missing or malformed payload leaves it empty and the
|
||||
// fallback chain below imports the geometry only.
|
||||
auto payload_it = model.model_info->metadata_items.find(ORCA_PUBLISHED_CONFIG_TAG);
|
||||
if (payload_it != model.model_info->metadata_items.end()) {
|
||||
try {
|
||||
ConfigSubstitutions payload_substitutions =
|
||||
config_loaded.load_from_ini_string(payload_it->second, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
config_substitutions.substitutions.insert(config_substitutions.substitutions.end(),
|
||||
std::make_move_iterator(payload_substitutions.begin()),
|
||||
std::make_move_iterator(payload_substitutions.end()));
|
||||
} catch (...) {
|
||||
// Ignore malformed published_config; the project still loads normally.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1. add extruder for prusa model if the number of existing extruders is not enough
|
||||
// 2. add extruder for BBS or Other model if only import geometry
|
||||
if (en_3mf_file_type == En3mfType::From_Prusa || (load_model && !load_config)) {
|
||||
@@ -8582,7 +8693,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
text += "\n";
|
||||
log_and_show_3mf_info(text, bambu_project_title);
|
||||
}
|
||||
} else if (load_config) {
|
||||
} else if (load_config && !published_config.published) {
|
||||
// BambuStudio version is older or same as our SLIC3R_VERSION
|
||||
wxString text = _L("The 3MF was created by BambuStudio. Some settings may differ from OrcaSlicer.");
|
||||
log_and_show_3mf_info(text, bambu_project_title);
|
||||
@@ -8642,7 +8753,9 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
}
|
||||
|
||||
Semver old_version(1, 5, 9);
|
||||
if ((en_3mf_file_type == En3mfType::From_BBS || en_3mf_file_type == En3mfType::From_Orca) && (file_version < old_version) && load_model && load_config && !config_loaded.empty()) {
|
||||
// A published 3MF has no project config to migrate: skip the old-version
|
||||
// translations even if a slicer tag slipped through classification.
|
||||
if ((en_3mf_file_type == En3mfType::From_BBS || en_3mf_file_type == En3mfType::From_Orca) && (file_version < old_version) && !published_config.published && load_model && load_config && !config_loaded.empty()) {
|
||||
translate_old = true;
|
||||
partplate_list.get_plate_size(current_width, current_depth, current_height);
|
||||
}
|
||||
@@ -8664,8 +8777,10 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
}
|
||||
}
|
||||
|
||||
// BBS:: project embedded presets
|
||||
if ((project_presets.size() > 0) && load_config) {
|
||||
// BBS:: project embedded presets (skipped for published projects: the author's
|
||||
// embedded presets must not pollute the receiver's library, the overlay applies
|
||||
// the published keys to the receiver's own presets instead).
|
||||
if ((project_presets.size() > 0) && load_config && !published_config.published) {
|
||||
// load project embedded presets
|
||||
PresetsConfigSubstitutions preset_substitutions;
|
||||
PresetBundle & preset_bundle = *wxGetApp().preset_bundle;
|
||||
@@ -8721,6 +8836,19 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
}
|
||||
}
|
||||
|
||||
// BBS: a "published" 3MF loads as a new project: its path must not become the
|
||||
// project filename (Save/Ctrl-S would overwrite the shared file), and the
|
||||
// published metadata is consumed above and stripped so a later save is a normal
|
||||
// unpublished 3MF.
|
||||
if (published_out != nullptr && published_config.published)
|
||||
*published_out = true;
|
||||
if (published_config.published && load_config && this->model.model_info != nullptr) {
|
||||
this->model.model_info->metadata_items.erase(ORCA_PUBLISHED_TAG);
|
||||
this->model.model_info->metadata_items.erase(ORCA_PUBLISHED_KEYS_TAG);
|
||||
this->model.model_info->metadata_items.erase(ORCA_PUBLISHED_MATERIAL_TAG);
|
||||
this->model.model_info->metadata_items.erase(ORCA_PUBLISHED_CONFIG_TAG);
|
||||
}
|
||||
|
||||
if (load_config) {
|
||||
if (!config.empty()) {
|
||||
Preset::normalize(config);
|
||||
@@ -8729,7 +8857,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
{
|
||||
// BBS: modify the prime tower params for old version file
|
||||
Semver old_version3(2, 0, 0);
|
||||
if ((en_3mf_file_type == En3mfType::From_BBS || en_3mf_file_type == En3mfType::From_Orca) && file_version < old_version3) {
|
||||
if ((en_3mf_file_type == En3mfType::From_BBS || en_3mf_file_type == En3mfType::From_Orca) && !published_config.published && file_version < old_version3) {
|
||||
double old_filament_prime_volume = 0.;
|
||||
int filament_count = 0;
|
||||
{
|
||||
@@ -8779,7 +8907,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
}
|
||||
|
||||
auto choise = wxGetApp().app_config->get("no_warn_when_modified_gcodes");
|
||||
if (choise.empty() || choise != "true") {
|
||||
if (!published_config.published && (choise.empty() || choise != "true")) {
|
||||
// BBS: first validate the printer
|
||||
// validate the system profiles
|
||||
std::set<std::string> modified_gcodes;
|
||||
@@ -8824,12 +8952,56 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
if (wipe_tower_y_opt)
|
||||
file_wipe_tower_y = *wipe_tower_y_opt;
|
||||
|
||||
// Convert the printer's filament ids to Orca ids before loading.
|
||||
if (auto* agent = wxGetApp().getAgent()) {
|
||||
if (auto* ids = config.opt<ConfigOptionStrings>("filament_ids"))
|
||||
for (std::string& id : ids->values)
|
||||
id = agent->to_orca_filament_id(id);
|
||||
}
|
||||
preset_bundle->load_config_model(filename.string(), std::move(config), file_version);
|
||||
preset_bundle->load_config_model(filename.string(), std::move(config), file_version, &published_config);
|
||||
|
||||
// Mixed-filament definitions that collided with one of the
|
||||
// receiver's real slots were relocated during the preset load.
|
||||
// Re-point the freshly parsed model's extruder references and
|
||||
// color painting from the author's slot numbers to where each
|
||||
// definition landed, so volumes colored with a mix follow it.
|
||||
// Runs before the objects are handed over to the plater below.
|
||||
if (load_model && !published_config.mixed_slot_relocations.empty())
|
||||
Slic3r::remap_model_filament_slots(model, published_config.mixed_slot_relocations);
|
||||
|
||||
// BBS: notify the user about published settings that could not be applied.
|
||||
if (!published_config.skipped_keys.empty()) {
|
||||
NotificationManager* notify_manager = q->get_notification_manager();
|
||||
std::string message = _u8L("Some published settings could not be applied:");
|
||||
for (const std::string& key : published_config.skipped_keys)
|
||||
message += "\n-" + key;
|
||||
// Informational: the load succeeded, these keys were skipped.
|
||||
notify_manager
|
||||
->bbl_show_3mf_warn_notification(message,
|
||||
NotificationManager::NotificationLevel::WarningNotificationLevel);
|
||||
}
|
||||
|
||||
// BBS: notify the user about slot materials that were replaced while
|
||||
// loading a published project (type mismatch / no same-type match).
|
||||
if (!published_config.material_replacements.empty()) {
|
||||
NotificationManager* notify_manager = q->get_notification_manager();
|
||||
std::string message = _u8L("Some filament slots were changed:");
|
||||
for (const std::string& replacement : published_config.material_replacements)
|
||||
message += "\n-" + replacement;
|
||||
// Informational: the load succeeded, the slots were adapted.
|
||||
notify_manager
|
||||
->bbl_show_3mf_warn_notification(message,
|
||||
NotificationManager::NotificationLevel::WarningNotificationLevel);
|
||||
}
|
||||
|
||||
// Remember the imported published selection so the Publish dialog is
|
||||
// pre-seeded with the file's settings. Stored after the load so any
|
||||
// per-slot relocations are already reflected in material_keys.
|
||||
if (published_config.published) {
|
||||
this->m_has_pending_published = true;
|
||||
this->m_pending_published_keys = published_config.published_keys;
|
||||
this->m_pending_material_keys = published_config.material_keys;
|
||||
}
|
||||
|
||||
ConfigOption* bed_type_opt = preset_bundle->project_config.option("curr_bed_type");
|
||||
if (bed_type_opt != nullptr) {
|
||||
@@ -8970,7 +9142,13 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
dynamic_map->value = false;
|
||||
}
|
||||
// Update filament combobox after loading config
|
||||
wxGetApp().plater()->sidebar().update_presets(Preset::TYPE_FILAMENT);
|
||||
if (published_config.published) {
|
||||
q->update_filament_colors_in_full_config();
|
||||
wxGetApp().plater()->sidebar().update_all_preset_comboboxes();
|
||||
wxGetApp().plater()->sidebar().update_dynamic_filament_list();
|
||||
} else {
|
||||
wxGetApp().plater()->sidebar().update_presets(Preset::TYPE_FILAMENT);
|
||||
}
|
||||
// The loaded project supplies nozzle_volume_type; refresh the sidebar
|
||||
// nozzle-count badges against it.
|
||||
if (auto *nozzle_volumes = wxGetApp().preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")) {
|
||||
@@ -9723,7 +9901,7 @@ fs::path Plater::priv::get_export_file_path(GUI::FileType file_type)
|
||||
return output_file;
|
||||
}
|
||||
|
||||
wxString Plater::priv::get_export_file(GUI::FileType file_type)
|
||||
wxString Plater::priv::get_export_file(GUI::FileType file_type, const wxString& title, bool published)
|
||||
{
|
||||
wxString wildcard;
|
||||
switch (file_type) {
|
||||
@@ -9765,8 +9943,11 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type)
|
||||
}
|
||||
case FT_3MF:
|
||||
{
|
||||
output_file.replace_extension("3mf");
|
||||
dlg_title = _L("Save file as");
|
||||
// A published export is suggested as "<name>.published.3mf" so the role is visible in the
|
||||
// dialog and in the recent-files list. This is only a pre-filled suggestion; the user's
|
||||
// typed filename wins, keeping a plain ".3mf" output fully valid.
|
||||
output_file.replace_extension(published ? "published.3mf" : "3mf");
|
||||
dlg_title = title.empty() ? _L("Save file as") : title;
|
||||
break;
|
||||
}
|
||||
case FT_OBJ:
|
||||
@@ -10004,6 +10185,11 @@ void Plater::priv::reset(bool apply_presets_change)
|
||||
|
||||
clear_warnings();
|
||||
|
||||
// A new project must not inherit the previous project's published selection (Feature A/B).
|
||||
m_has_pending_published = false;
|
||||
m_pending_published_keys.clear();
|
||||
m_pending_material_keys.clear();
|
||||
|
||||
set_project_filename("");
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " call set_project_filename: empty";
|
||||
|
||||
@@ -15104,14 +15290,15 @@ void Plater::load_project(wxString const& filename2,
|
||||
if (strategy & LoadStrategy::Restore)
|
||||
input_paths.push_back(into_u8(originfile));
|
||||
|
||||
std::vector<size_t> res = load_files(input_paths, strategy);
|
||||
bool loaded_published = false;
|
||||
std::vector<size_t> res = load_files(input_paths, strategy, false, &loaded_published);
|
||||
|
||||
reset_project_dirty_initial_presets();
|
||||
update_project_dirty_from_presets();
|
||||
wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config);
|
||||
|
||||
// if res is empty no data has been loaded
|
||||
if (!res.empty() && (load_restore || !(strategy & LoadStrategy::Silence))) {
|
||||
if (!res.empty() && !loaded_published && (load_restore || !(strategy & LoadStrategy::Silence))) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " call set_project_filename: " << (load_restore ? originfile : filename);
|
||||
p->set_project_filename(load_restore ? originfile : filename);
|
||||
if (load_restore && originfile.IsEmpty()) {
|
||||
@@ -15122,6 +15309,15 @@ void Plater::load_project(wxString const& filename2,
|
||||
if (using_exported_file()) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " using ecported set project filename: " << filename;
|
||||
p->set_project_filename(filename);
|
||||
} else if (loaded_published && !res.empty()) {
|
||||
// A "published" 3MF loads as a new project: its path must not become the project
|
||||
// filename (Save/Ctrl-S prompts for a destination instead of overwriting it);
|
||||
// reset() already cleared the project name, so restore the default title and keep
|
||||
// the file in recents. Only on a successful load (res not empty): a failed or
|
||||
// cancelled load must not pollute "Recently opened".
|
||||
p->set_project_name(_L("Untitled"));
|
||||
if (!filename.IsEmpty())
|
||||
wxGetApp().mainframe->add_to_recent_projects(filename);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16779,22 +16975,12 @@ void Plater::force_update_all_plate_thumbnails()
|
||||
}
|
||||
|
||||
// BBS: backup
|
||||
std::vector<size_t> Plater::load_files(const std::vector<fs::path>& input_files, LoadStrategy strategy, bool ask_multi) {
|
||||
std::vector<size_t> Plater::load_files(const std::vector<fs::path>& input_files, LoadStrategy strategy, bool ask_multi, bool* published_out) {
|
||||
//BBS: wish to reset state when load a new file
|
||||
p->m_slice_all_only_has_gcode = false;
|
||||
//BBS: wish to reset all plates stats item selected state when load a new file
|
||||
p->preview->get_canvas3d()->reset_select_plate_toolbar_selection();
|
||||
return p->load_files(input_files, strategy, ask_multi);
|
||||
}
|
||||
|
||||
// To be called when providing a list of files to the GUI slic3r on command line.
|
||||
std::vector<size_t> Plater::load_files(const std::vector<std::string>& input_files, LoadStrategy strategy, bool ask_multi)
|
||||
{
|
||||
std::vector<fs::path> paths;
|
||||
paths.reserve(input_files.size());
|
||||
for (const std::string& path : input_files)
|
||||
paths.emplace_back(path);
|
||||
return p->load_files(paths, strategy, ask_multi);
|
||||
return p->load_files(input_files, strategy, ask_multi, published_out);
|
||||
}
|
||||
|
||||
bool Plater::preview_zip_archive(const boost::filesystem::path& archive_path)
|
||||
@@ -18023,7 +18209,6 @@ void Plater::send_gcode_finish(wxString name)
|
||||
auto out_str = GUI::format(_L("The file %s has been sent to the printer's storage space and can be viewed on the printer."), name);
|
||||
p->notification_manager->push_exporting_finished_notification(out_str, "", false);
|
||||
}
|
||||
|
||||
void Plater::export_core_3mf()
|
||||
{
|
||||
wxString path = p->get_export_file(FT_3MF);
|
||||
@@ -18032,6 +18217,182 @@ void Plater::export_core_3mf()
|
||||
export_3mf(path_u8, SaveStrategy::Silence);
|
||||
}
|
||||
|
||||
// Export the current project as a "published" 3MF: a pure export that never touches the
|
||||
// project's file name, dirty state, backup path or title, and attaches the published metadata
|
||||
// to the model only for the duration of the export (a later Save Project is a normal 3MF).
|
||||
int Plater::export_published_3mf(const std::vector<std::string>& published_keys,
|
||||
const std::vector<Slic3r::PublishedMaterialEntry>& material_keys)
|
||||
{
|
||||
wxString path = p->get_export_file(FT_3MF, _L("Publish 3MF file as:"), true);
|
||||
if (path.empty() || path == "<cancel>")
|
||||
return wxID_CANCEL;
|
||||
|
||||
nlohmann::json j = nlohmann::json::array();
|
||||
for (const std::string& key : published_keys)
|
||||
j.push_back(key);
|
||||
nlohmann::json jm = nlohmann::json::array();
|
||||
for (const Slic3r::PublishedMaterialEntry& e : material_keys)
|
||||
jm.push_back({{"material",
|
||||
{{"filament_type", e.filament_type},
|
||||
{"filament_vendor", e.filament_vendor},
|
||||
{"filament_id", e.filament_id},
|
||||
{"setting_id", e.setting_id},
|
||||
{"name", e.preset_name}}},
|
||||
{"slot", e.slot},
|
||||
{"keys", e.keys},
|
||||
{"full", e.full},
|
||||
{"full_keys", e.full_keys},
|
||||
{"publish_type", e.publish_type},
|
||||
{"type", e.publish_type_value},
|
||||
{"publish_color", e.publish_color},
|
||||
{"color", e.color}});
|
||||
|
||||
Model& model = this->model();
|
||||
// Save the previous metadata so it can be restored after the export, keeping the in-memory
|
||||
// project pristine (the published flag lives only in the exported file).
|
||||
const bool had_model_info = (model.model_info != nullptr);
|
||||
const bool had_published = had_model_info &&
|
||||
(model.model_info->metadata_items.find(ORCA_PUBLISHED_TAG) != model.model_info->metadata_items.end());
|
||||
const bool had_published_keys = had_model_info && (model.model_info->metadata_items.find(ORCA_PUBLISHED_KEYS_TAG) !=
|
||||
model.model_info->metadata_items.end());
|
||||
const bool had_material_keys = had_model_info && (model.model_info->metadata_items.find(ORCA_PUBLISHED_MATERIAL_TAG) !=
|
||||
model.model_info->metadata_items.end());
|
||||
const bool had_payload = had_model_info &&
|
||||
(model.model_info->metadata_items.find(ORCA_PUBLISHED_CONFIG_TAG) != model.model_info->metadata_items.end());
|
||||
const std::string prev_published = had_published ? model.model_info->metadata_items.at(ORCA_PUBLISHED_TAG) : std::string();
|
||||
const std::string prev_published_keys = had_published_keys ? model.model_info->metadata_items.at(ORCA_PUBLISHED_KEYS_TAG) :
|
||||
std::string();
|
||||
const std::string prev_material_keys = had_material_keys ? model.model_info->metadata_items.at(ORCA_PUBLISHED_MATERIAL_TAG) :
|
||||
std::string();
|
||||
const std::string prev_payload = had_payload ? model.model_info->metadata_items.at(ORCA_PUBLISHED_CONFIG_TAG) : std::string();
|
||||
|
||||
// export_3mf() assigns archive paths to previously unsaved SVGs. Preserve those fields too,
|
||||
// otherwise a publish changes what a later normal project save writes.
|
||||
std::vector<std::pair<std::string*, std::string>> previous_svg_paths;
|
||||
for (ModelObject* object : model.objects)
|
||||
for (ModelVolume* volume : object->volumes)
|
||||
if (volume != nullptr && volume->emboss_shape.has_value() && volume->emboss_shape->svg_file.has_value()) {
|
||||
std::string* path_in_3mf = &volume->emboss_shape->svg_file->path_in_3mf;
|
||||
previous_svg_paths.emplace_back(path_in_3mf, *path_in_3mf);
|
||||
}
|
||||
|
||||
auto restore_temporary_state = [&]() {
|
||||
for (const auto& [path_in_3mf, previous_path] : previous_svg_paths)
|
||||
*path_in_3mf = previous_path;
|
||||
|
||||
if (!had_model_info) {
|
||||
model.model_info = nullptr;
|
||||
} else {
|
||||
if (had_published)
|
||||
model.model_info->metadata_items[ORCA_PUBLISHED_TAG] = prev_published;
|
||||
else
|
||||
model.model_info->metadata_items.erase(ORCA_PUBLISHED_TAG);
|
||||
if (had_published_keys)
|
||||
model.model_info->metadata_items[ORCA_PUBLISHED_KEYS_TAG] = prev_published_keys;
|
||||
else
|
||||
model.model_info->metadata_items.erase(ORCA_PUBLISHED_KEYS_TAG);
|
||||
if (had_material_keys)
|
||||
model.model_info->metadata_items[ORCA_PUBLISHED_MATERIAL_TAG] = prev_material_keys;
|
||||
else
|
||||
model.model_info->metadata_items.erase(ORCA_PUBLISHED_MATERIAL_TAG);
|
||||
if (had_payload)
|
||||
model.model_info->metadata_items[ORCA_PUBLISHED_CONFIG_TAG] = prev_payload;
|
||||
else
|
||||
model.model_info->metadata_items.erase(ORCA_PUBLISHED_CONFIG_TAG);
|
||||
}
|
||||
};
|
||||
bool state_restored = false;
|
||||
auto restore_now = [&]() {
|
||||
if (state_restored)
|
||||
return;
|
||||
restore_temporary_state();
|
||||
state_restored = true;
|
||||
};
|
||||
ScopeGuard restore_guard(restore_now);
|
||||
|
||||
if (model.model_info == nullptr)
|
||||
model.model_info = std::make_shared<ModelInfo>();
|
||||
model.model_info->metadata_items[ORCA_PUBLISHED_TAG] = "1";
|
||||
model.model_info->metadata_items[ORCA_PUBLISHED_KEYS_TAG] = j.dump();
|
||||
model.model_info->metadata_items[ORCA_PUBLISHED_MATERIAL_TAG] = jm.dump();
|
||||
|
||||
int ret = -1;
|
||||
try {
|
||||
// Minimal published export: filter full_config to the published keys, material keys,
|
||||
// identity fields and plate geometry keys, and omit the project config file, the
|
||||
// project-embedded preset dumps and the OrcaSlicer version tag from the archive. The
|
||||
// filtered values are serialized into the published_config metadata payload instead, so
|
||||
// OrcaSlicer versions without the publish feature fall back to importing the geometry only
|
||||
// (keeping the receiver's presets) while new versions rebuild the config from the payload.
|
||||
DynamicPrintConfig full_cfg = wxGetApp().preset_bundle->full_config_secure();
|
||||
DynamicPrintConfig filtered_cfg = filter_published_config(full_cfg, published_keys, material_keys);
|
||||
std::string payload;
|
||||
for (const std::string& key : filtered_cfg.keys()) {
|
||||
// A value containing a newline would break the INI written below (read_ini throws),
|
||||
// so load_from_ini_string discards the whole settings block on import. Skip such
|
||||
// keys instead of silently dropping every setting.
|
||||
std::string value = filtered_cfg.opt_serialize(key);
|
||||
if (value.find('\n') != std::string::npos) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "publish: dropping key \"" << key
|
||||
<< "\" from the published payload (value contains a newline)";
|
||||
continue;
|
||||
}
|
||||
payload += key + " = " + value + "\n";
|
||||
}
|
||||
model.model_info->metadata_items[ORCA_PUBLISHED_CONFIG_TAG] = std::move(payload);
|
||||
|
||||
// Same file layout as save_project(), plus Silence (so export_3mf does not set the project
|
||||
// filename on success, keeping this a pure export like export_core_3mf()) and MinimalPublished.
|
||||
auto save_strategy = SaveStrategy::SplitModel | SaveStrategy::ShareMesh | SaveStrategy::Silence | SaveStrategy::MinimalPublished;
|
||||
bool full_pathnames = wxGetApp().app_config->get_bool("export_sources_full_pathnames");
|
||||
if (full_pathnames)
|
||||
save_strategy = save_strategy | SaveStrategy::FullPathSources;
|
||||
ret = export_3mf(into_path(path), save_strategy, -1, nullptr);
|
||||
} catch (...) {
|
||||
restore_now();
|
||||
MessageDialog(this,
|
||||
_L("Failed to export the published 3MF file.\nPlease check whether the folder exists online or if other programs "
|
||||
"have the file open."),
|
||||
_L("Publish"), wxOK | wxICON_WARNING)
|
||||
.ShowModal();
|
||||
return wxID_CANCEL;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
restore_now();
|
||||
MessageDialog(this,
|
||||
_L("Failed to export the published 3MF file.\nPlease check whether the folder exists online or if other programs "
|
||||
"have the file open."),
|
||||
_L("Publish"), wxOK | wxICON_WARNING)
|
||||
.ShowModal();
|
||||
return wxID_CANCEL;
|
||||
}
|
||||
restore_now();
|
||||
|
||||
// Register the exported file in the "Recently opened" list
|
||||
wxGetApp().mainframe->add_to_recent_projects(path);
|
||||
|
||||
return wxID_YES;
|
||||
}
|
||||
|
||||
bool Plater::get_pending_published(std::vector<std::string>& out_keys,
|
||||
std::vector<Slic3r::PublishedMaterialEntry>& out_material) const
|
||||
{
|
||||
if (!p->m_has_pending_published)
|
||||
return false;
|
||||
out_keys = p->m_pending_published_keys;
|
||||
out_material = p->m_pending_material_keys;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Plater::set_pending_published(const std::vector<std::string>& published_keys,
|
||||
const std::vector<Slic3r::PublishedMaterialEntry>& material_keys)
|
||||
{
|
||||
p->m_has_pending_published = true;
|
||||
p->m_pending_published_keys = published_keys;
|
||||
p->m_pending_material_keys = material_keys;
|
||||
}
|
||||
|
||||
Preset *get_printer_preset(const MachineObject *obj)
|
||||
{
|
||||
if (!obj)
|
||||
|
||||
@@ -407,9 +407,7 @@ public:
|
||||
bool preview_zip_archive(const boost::filesystem::path& archive_path);
|
||||
|
||||
// BBS: restore
|
||||
std::vector<size_t> load_files(const std::vector<boost::filesystem::path>& input_files, LoadStrategy strategy = LoadStrategy::LoadModel | LoadStrategy::LoadConfig, bool ask_multi = false);
|
||||
// To be called when providing a list of files to the GUI slic3r on command line.
|
||||
std::vector<size_t> load_files(const std::vector<std::string>& input_files, LoadStrategy strategy = LoadStrategy::LoadModel | LoadStrategy::LoadConfig, bool ask_multi = false);
|
||||
std::vector<size_t> load_files(const std::vector<boost::filesystem::path>& input_files, LoadStrategy strategy = LoadStrategy::LoadModel | LoadStrategy::LoadConfig, bool ask_multi = false, bool* published_out = nullptr);
|
||||
// to be called on drag and drop
|
||||
bool load_files(const wxArrayString& filenames);
|
||||
|
||||
@@ -519,6 +517,13 @@ public:
|
||||
void export_gcode_3mf(bool export_all = false);
|
||||
void send_gcode_finish(wxString name);
|
||||
void export_core_3mf();
|
||||
// Export a "published" 3MF embedding the author-selected settings in the file metadata; a
|
||||
// pure export that leaves the in-memory project untouched.
|
||||
int export_published_3mf(const std::vector<std::string>& published_keys, const std::vector<Slic3r::PublishedMaterialEntry>& material_keys);
|
||||
// Session-level stash of the last published selection, seeded into the Publish dialog on
|
||||
// open and written on publish or on loading a published 3MF
|
||||
bool get_pending_published(std::vector<std::string>& out_keys, std::vector<Slic3r::PublishedMaterialEntry>& out_material) const;
|
||||
void set_pending_published(const std::vector<std::string>& published_keys, const std::vector<Slic3r::PublishedMaterialEntry>& material_keys);
|
||||
static TriangleMesh combine_mesh_fff(const ModelObject& mo, int instance_id, std::function<void(const std::string&)> notify_func = {});
|
||||
void export_stl(bool extended = false, bool selection_only = false, bool multi_stls = false, FileType file_type = FT_STL);
|
||||
//BBS: remove amf
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,286 @@
|
||||
#pragma once
|
||||
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "Widgets/TabCtrl.hpp"
|
||||
|
||||
#include "libslic3r/PublishSettings.hpp"
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/colour.h>
|
||||
#include <wx/scrolwin.h>
|
||||
#include <wx/menu.h>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
// Forward declarations (all are global classes, see Widgets/TextInput.hpp and
|
||||
// Widgets/StaticLine.hpp).
|
||||
class TextInput;
|
||||
class StaticLine;
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
struct PublishMaterialIdentity
|
||||
{
|
||||
std::string type;
|
||||
std::string vendor;
|
||||
std::string id;
|
||||
};
|
||||
|
||||
// One unmet dependency of an enabled mixed-filament slot: the component filament the mix uses
|
||||
// would ship without its material (either the component slot is not enabled at all, or it is
|
||||
// enabled with neither "Full Publish" nor the "Type" requirement checked). Slots are 0-based.
|
||||
struct MixedDependencyIssue
|
||||
{
|
||||
enum class Reason { Disabled, MaterialNotPublished };
|
||||
size_t mixed_slot{0};
|
||||
size_t component_slot{0};
|
||||
Reason reason{Reason::Disabled};
|
||||
};
|
||||
|
||||
// Dialog letting a model author select which settings get embedded in a 3MF. Nested tab layout
|
||||
// mirroring the Process settings (Printer / Filament / Process outer tabs, category or material
|
||||
// tabs inside each). Dirty settings are pre-checked and shown bold; on OK the print rows become
|
||||
// "orca_published_keys" and the material rows become "orca_published_material_keys".
|
||||
class PublishSettingsDialog : public DPIDialog
|
||||
{
|
||||
public:
|
||||
// Optional published selection (Feature A/B): when the caller supplies one (either a
|
||||
// remembered session selection or the payload of a freshly loaded published 3MF) the dialog
|
||||
// is seeded from it, overriding the dirty-default pre-check. A non-null pointer to an empty
|
||||
// selection means "publish nothing" (an intentional empty state); a null pointer means "no
|
||||
// remembered selection" (keep the dirty defaults).
|
||||
PublishSettingsDialog(wxWindow* parent = nullptr,
|
||||
const std::vector<std::string>* published_keys = nullptr,
|
||||
const std::vector<Slic3r::PublishedMaterialEntry>* material_keys = nullptr);
|
||||
~PublishSettingsDialog();
|
||||
|
||||
// The selected print/printer setting keys (in display order); printer keys carry a '#N'
|
||||
// per-extruder suffix.
|
||||
std::vector<std::string> GetPublishedKeys() const;
|
||||
|
||||
// The selected keys grouped per material section (base keys, no '#N' suffix).
|
||||
std::vector<Slic3r::PublishedMaterialEntry> GetPublishedMaterialKeys() const;
|
||||
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
void on_sys_color_changed() override;
|
||||
|
||||
private:
|
||||
void fit_to_content();
|
||||
void refresh_mixed_tab_bitmaps();
|
||||
|
||||
// Which part of the settings the row/category came from.
|
||||
enum class Section { Print, Printer, Material };
|
||||
|
||||
// One selectable setting row: a checkbox (setting name) plus a value label and an optional
|
||||
// grey unit label. key is the full config key, possibly with a "#N" variant suffix
|
||||
// (print/printer rows); material rows carry the base key.
|
||||
enum class RowKind {
|
||||
Setting, // a regular setting key
|
||||
Color, // material colour requirement (filament_colour)
|
||||
Type, // material type requirement (read-only text)
|
||||
};
|
||||
struct Row
|
||||
{
|
||||
std::string key;
|
||||
wxString category;
|
||||
wxString subcategory;
|
||||
wxString label;
|
||||
wxString value;
|
||||
wxString unit;
|
||||
wxString section_title; // outer tab title, for filter matching
|
||||
Section section{Section::Print};
|
||||
RowKind kind{RowKind::Setting};
|
||||
size_t outer_index{0};
|
||||
size_t inner_index{0};
|
||||
bool dirty{false}; // matches a dirty base key: pre-checked + bold
|
||||
bool matches_filter{false}; // survives the active filter (computed by apply_filter)
|
||||
wxCheckBox* check{nullptr};
|
||||
wxStaticText* value_label{nullptr};
|
||||
wxStaticText* unit_label{nullptr};
|
||||
wxStaticBitmap* color_chip{nullptr}; // Color rows only; swatch next to the value
|
||||
wxSizerItem* item{nullptr}; // sizer item of this row's h-sizer in its tab list sizer
|
||||
};
|
||||
|
||||
// An optgroup heading. Rows store indices into m_rows.
|
||||
struct Subcategory
|
||||
{
|
||||
wxString title;
|
||||
::StaticLine* header{nullptr}; // null when the title is empty
|
||||
wxSizerItem* item{nullptr};
|
||||
std::vector<size_t> rows;
|
||||
};
|
||||
|
||||
// An inner TabCtrl page with its material controls and content.
|
||||
struct Category
|
||||
{
|
||||
wxString title;
|
||||
Section section{Section::Print};
|
||||
size_t group{0}; // index into m_sections / outer page
|
||||
size_t source_index{0}; // stable source page or material slot index
|
||||
wxPanel* page{nullptr};
|
||||
wxScrolledWindow* scroll{nullptr};
|
||||
wxBoxSizer* list_sizer{nullptr};
|
||||
wxStaticText* info{nullptr};
|
||||
wxPoint scroll_pos{0, 0};
|
||||
wxStaticBitmap* filament_color_chip{nullptr};
|
||||
wxStaticText* title_label{nullptr}; // material title (static text; Full Publish carries the label elsewhere)
|
||||
// "Enable": while unchecked nothing of this slot is exported and everything below the
|
||||
// header row is hidden. For physical slots the Full Publish toggle sits on a second
|
||||
// line (full_line_item) visible only when enabled; for mixed slots Enable alone implies
|
||||
// publishing the mix definition, so no Full Publish widget exists at all.
|
||||
wxCheckBox* enable_check{nullptr};
|
||||
wxSizerItem* full_line_item{nullptr}; // sizer item of the Full Publish line (physical slots only)
|
||||
// "Full Publish": while checked, the whole slot preset is serialized and its rows
|
||||
// (incl. Color/Type) are disabled.
|
||||
wxCheckBox* full_check{nullptr};
|
||||
// True for a mixed-color filament slot: no Material/Retraction rows; Enable publishes
|
||||
// the slot's gradient/ratio definition as a whole.
|
||||
bool is_mixed{false};
|
||||
// Material identity, only for Section::Material categories.
|
||||
std::string filament_type;
|
||||
std::string filament_vendor;
|
||||
std::string filament_id;
|
||||
// The author's 0-based filament slot this material section represents.
|
||||
size_t filament_slot{0};
|
||||
std::vector<Subcategory> subs;
|
||||
std::vector<size_t> rows; // flattened rows of this category
|
||||
};
|
||||
|
||||
// Frozen snapshot of a mixed filament slot's definition for the read-only visualization
|
||||
// painted on the slot's page. Plain data only: the paint handler must never touch the
|
||||
// config. For gradient slots the curve is pre-sampled (t, ratio) pairs, where ratio is the
|
||||
// first component's share over model height; anchors carry the raw control points.
|
||||
struct MixedVisualSpec
|
||||
{
|
||||
bool valid{false};
|
||||
bool is_gradient{false};
|
||||
std::vector<wxColour> component_colours; // colour per component, in config order
|
||||
std::vector<double> ratios; // sublayer shares summing to ~1 (non-gradient)
|
||||
std::vector<double> tri_weights; // 3-component mixes: barycentric shares
|
||||
std::vector<std::pair<double, double>> gradient_samples;
|
||||
std::vector<std::pair<double, double>> gradient_anchors;
|
||||
};
|
||||
|
||||
// One outer TabCtrl page. Category entries are its inner tabs.
|
||||
struct SectionGroup
|
||||
{
|
||||
wxString title; // _L("Printer") / _L("Filament") / _L("Process")
|
||||
Section kind{Section::Print}; // maps 1:1 to the display group
|
||||
std::string icon_name; // "printer" / "filament" / "process"
|
||||
ScalableBitmap icon_bmp; // tab icon next to the title; rescaled on DPI change
|
||||
wxPanel* page{nullptr};
|
||||
TabCtrl* tabs{nullptr};
|
||||
// Second tab strip, below the main one, listing only the mixed-color filament slots.
|
||||
// Present on the Material section only (null elsewhere).
|
||||
TabCtrl* mixed_tabs{nullptr};
|
||||
wxPanel* page_host{nullptr};
|
||||
wxBoxSizer* page_host_sizer{nullptr};
|
||||
int selected_inner{-1};
|
||||
// Selected mixed tab (index into mixed_categories), valid while a mixed slot page is shown.
|
||||
int selected_mixed{-1};
|
||||
std::vector<size_t> categories; // indices into m_categories (physical slots)
|
||||
std::vector<size_t> mixed_categories; // indices into m_categories (mixed slots)
|
||||
};
|
||||
|
||||
void build_option_model();
|
||||
// Seed the dialog from a published selection (print/printer keys + per-slot material keys):
|
||||
// the supplied selection is authoritative - it is applied after the dirty pre-check and
|
||||
// overrides it, so deselected dirty keys stay off. Rows/slots not present in the selection
|
||||
// are left unselected. Unknown or out-of-range entries are skipped gracefully.
|
||||
void apply_selection(const std::vector<std::string>& published_keys,
|
||||
const std::vector<Slic3r::PublishedMaterialEntry>& material_keys);
|
||||
// Frozen snapshot of a mixed slot's definition for the page visualization, resolved from
|
||||
// the full config once at dialog-build time. Gradient slots pre-sample exactly what the
|
||||
// slicer will print: the custom curve wins over the gradient_range endpoints over the
|
||||
// 0.10 -> 0.90 default (the resolution FilamentBitmapUtils::mixed_gradient_curve mirrors).
|
||||
static MixedVisualSpec make_mixed_visual_spec(const Slic3r::DynamicPrintConfig& full, size_t slot);
|
||||
void apply_filter(const wxString& filter_text);
|
||||
// Menu-only pseudo filters: show only the checked ("Filter selected") or only the
|
||||
// unchecked ("Filter non-selected") rows. The search box keeps the user's text.
|
||||
void apply_pseudo_filter(bool selected_only);
|
||||
// Recompute row matches and visibility for the active filter mode. filter is the lowered
|
||||
// search text; it is ignored by the pseudo modes.
|
||||
void refresh_filter(const wxString& filter);
|
||||
void select_all(bool value);
|
||||
void select_visible(bool value);
|
||||
void show_menu(wxMouseEvent& evt);
|
||||
void set_row_bold(Row& row, bool bold);
|
||||
// "Full Publish" toggled: disables/enables the material's rows.
|
||||
void on_full_toggle(size_t category_index);
|
||||
// "Enable" toggled on a material slot: reveals/hides everything below the header and, for a
|
||||
// mixed slot, auto-selects its component filaments' "Enable" + "Full Publish" toggles.
|
||||
void on_enable_toggle(size_t category_index);
|
||||
// Whether a category currently publishes something, driving its tab's indicator dot.
|
||||
// Print/Printer: any row checked. Material: the slot's "Enable" is on.
|
||||
bool category_has_selection(const Category& cat) const;
|
||||
// Recompute the indicator dot on every outer/inner tab from the current selection state.
|
||||
void refresh_tab_indicators();
|
||||
// Unmet dependencies of enabled mixed-filament slots, one record per (mix, component) pair:
|
||||
// "Enable" not checked on the component, or enabled with neither "Full Publish" nor the
|
||||
// "Type" requirement row checked. Colour is deliberately ignored (the receiver renders the
|
||||
// mix from its own components' colours). Sorted by mixed slot, then component slot.
|
||||
std::vector<MixedDependencyIssue> unpublished_mixed_components() const;
|
||||
// Read-only visualization of a mixed slot's definition (a stacked ratio bar, or the
|
||||
// Material Ratio vs Model Height graph for a gradient), inserted above the info hint
|
||||
// inside the category's scroll area.
|
||||
void add_mixed_visual(size_t category_index, const MixedVisualSpec& spec);
|
||||
// Return/create the fixed outer page for a Section kind.
|
||||
size_t section_group_for(Section kind);
|
||||
size_t category_index_for(const wxString& title,
|
||||
Section section,
|
||||
size_t group,
|
||||
size_t source_index,
|
||||
const PublishMaterialIdentity& identity = PublishMaterialIdentity(),
|
||||
bool is_mixed = false);
|
||||
size_t subcategory_index_for(size_t category_index, const wxString& title, const wxString& icon);
|
||||
void add_row_ui(const std::string& key,
|
||||
const wxString& label,
|
||||
const wxString& value,
|
||||
const wxString& unit,
|
||||
size_t category_index,
|
||||
size_t subcategory_index,
|
||||
RowKind kind = RowKind::Setting);
|
||||
// The non-structural filament keys of a slot's preset, for a "Full Publish" entry.
|
||||
std::vector<std::string> full_keys_for_slot() const;
|
||||
void save_scroll_position(Category& category);
|
||||
void show_outer_page(size_t section_index);
|
||||
void show_inner_page(size_t section_index, int inner_index);
|
||||
void show_mixed_page(size_t section_index, int mixed_index);
|
||||
void on_outer_tab_changed(wxCommandEvent& event);
|
||||
void on_inner_tab_changed(size_t section_index, wxCommandEvent& event);
|
||||
void on_mixed_tab_changed(size_t section_index, wxCommandEvent& event);
|
||||
bool row_is_visible(const Row& row) const;
|
||||
void apply_visibility();
|
||||
void bind_tab_events();
|
||||
|
||||
TabCtrl* m_outer_tabs{nullptr};
|
||||
wxPanel* m_outer_host{nullptr};
|
||||
wxBoxSizer* m_outer_host_sizer{nullptr};
|
||||
int m_selected_outer{-1};
|
||||
wxBoxSizer* m_fb_sizer{nullptr}; // "All"/"None" buttons sizer
|
||||
// Active filter mode: free text from the search box, or one of the menu's pseudo filters.
|
||||
enum class FilterMode { Text, SelectedOnly, UnselectedOnly };
|
||||
FilterMode m_filter_mode{FilterMode::Text};
|
||||
TextInput* m_filter_box{nullptr};
|
||||
wxTextCtrl* m_filter_ctrl{nullptr};
|
||||
wxStaticBitmap* m_menu_button{nullptr};
|
||||
// Shown while a pseudo filter is active (the search box keeps the user's text, so the chip
|
||||
// carries the visible state); clicking it returns to text filtering.
|
||||
wxStaticText* m_pseudo_chip{nullptr};
|
||||
wxString m_info_nonsel;
|
||||
wxString m_info_allsel;
|
||||
wxString m_info_empty;
|
||||
wxString m_info_mix; // body hint shown for a mixed slot (published as a whole)
|
||||
|
||||
ScalableBitmap m_search;
|
||||
ScalableBitmap m_menu;
|
||||
|
||||
std::vector<Row> m_rows;
|
||||
std::vector<Category> m_categories;
|
||||
std::vector<SectionGroup> m_sections;
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
@@ -814,13 +814,13 @@ wxBoxSizer* SendMultiMachinePage::create_item_title(wxString title, wxWindow* pa
|
||||
wxBoxSizer* m_sizer_title = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
auto m_title = new wxStaticText(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_title->SetForegroundColour(DESIGN_GRAY800_COLOR);
|
||||
m_title->SetForegroundColour(SEND_DESIGN_GRAY800_COLOR);
|
||||
m_title->SetFont(::Label::Head_13);
|
||||
m_title->Wrap(-1);
|
||||
m_title->SetToolTip(tooltip);
|
||||
|
||||
auto m_line = new wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL);
|
||||
m_line->SetBackgroundColour(DESIGN_GRAY400_COLOR);
|
||||
m_line->SetBackgroundColour(SEND_DESIGN_GRAY400_COLOR);
|
||||
|
||||
m_sizer_title->Add(m_title, 0, wxALIGN_CENTER | wxALL, 3);
|
||||
m_sizer_title->Add(0, 0, 0, wxLEFT, 9);
|
||||
@@ -843,7 +843,7 @@ wxBoxSizer* SendMultiMachinePage::create_item_checkbox(wxString title, wxWindow*
|
||||
m_sizer_checkbox->Add(0, 0, 0, wxEXPAND | wxLEFT, 8);
|
||||
|
||||
auto checkbox_title = new wxStaticText(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, 0);
|
||||
checkbox_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
|
||||
checkbox_title->SetForegroundColour(SEND_DESIGN_GRAY900_COLOR);
|
||||
checkbox_title->SetFont(::Label::Body_13);
|
||||
|
||||
auto size = checkbox_title->GetTextExtent(title);
|
||||
@@ -867,12 +867,12 @@ wxBoxSizer* SendMultiMachinePage::create_item_input(wxString str_before, wxStrin
|
||||
{
|
||||
wxBoxSizer* sizer_input = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto input_title = new wxStaticText(parent, wxID_ANY, str_before);
|
||||
input_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
|
||||
input_title->SetForegroundColour(SEND_DESIGN_GRAY900_COLOR);
|
||||
input_title->SetFont(::Label::Body_13);
|
||||
input_title->SetToolTip(tooltip);
|
||||
input_title->Wrap(-1);
|
||||
|
||||
auto input = new ::TextInput(parent, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, DESIGN_INPUT_SIZE, wxTE_PROCESS_ENTER);
|
||||
auto input = new ::TextInput(parent, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, SEND_DESIGN_INPUT_SIZE, wxTE_PROCESS_ENTER);
|
||||
StateColor input_bg(std::pair<wxColour, int>(wxColour("#F0F0F1"), StateColor::Disabled), std::pair<wxColour, int>(*wxWHITE, StateColor::Enabled));
|
||||
input->SetBackgroundColor(input_bg);
|
||||
input->GetTextCtrl()->SetValue(app_config->get(param));
|
||||
@@ -880,7 +880,7 @@ wxBoxSizer* SendMultiMachinePage::create_item_input(wxString str_before, wxStrin
|
||||
input->GetTextCtrl()->SetValidator(validator);
|
||||
|
||||
auto second_title = new wxStaticText(parent, wxID_ANY, str_after, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END);
|
||||
second_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
|
||||
second_title->SetForegroundColour(SEND_DESIGN_GRAY900_COLOR);
|
||||
second_title->SetFont(::Label::Body_13);
|
||||
second_title->SetToolTip(tooltip);
|
||||
second_title->Wrap(-1);
|
||||
@@ -1337,7 +1337,7 @@ wxPanel* SendMultiMachinePage::create_page()
|
||||
m_tip_text->SetMinSize(wxSize(FromDIP(DEVICE_ITEM_MAX_WIDTH), -1));
|
||||
m_tip_text->SetMaxSize(wxSize(FromDIP(DEVICE_ITEM_MAX_WIDTH), -1));
|
||||
m_tip_text->SetLabel(_L("Please select the devices you would like to manage here (up to 6 devices)"));
|
||||
m_tip_text->SetForegroundColour(DESIGN_GRAY800_COLOR);
|
||||
m_tip_text->SetForegroundColour(SEND_DESIGN_GRAY800_COLOR);
|
||||
m_tip_text->SetFont(::Label::Head_20);
|
||||
m_tip_text->Wrap(-1);
|
||||
|
||||
|
||||
@@ -22,15 +22,15 @@ namespace GUI {
|
||||
#define SEND_LEFT_DEV_STATUS 250
|
||||
#define SEND_LEFT_TAKS_STATUS 180
|
||||
|
||||
#define DESIGN_SELECTOR_NOMORE_COLOR wxColour(248, 248, 248)
|
||||
#define DESIGN_GRAY900_COLOR wxColour(38, 46, 48)
|
||||
#define DESIGN_GRAY800_COLOR wxColour(50, 58, 61)
|
||||
#define DESIGN_GRAY600_COLOR wxColour(144, 144, 144)
|
||||
#define DESIGN_GRAY400_COLOR wxColour(166, 169, 170)
|
||||
#define DESIGN_RESOUTION_PREFERENCES wxSize(FromDIP(540), -1)
|
||||
#define DESIGN_COMBOBOX_SIZE wxSize(FromDIP(140), -1)
|
||||
#define DESIGN_LARGE_COMBOBOX_SIZE wxSize(FromDIP(160), -1)
|
||||
#define DESIGN_INPUT_SIZE wxSize(FromDIP(50), -1)
|
||||
#define SEND_DESIGN_SELECTOR_NOMORE_COLOR wxColour(248, 248, 248)
|
||||
#define SEND_DESIGN_GRAY900_COLOR wxColour(38, 46, 48)
|
||||
#define SEND_DESIGN_GRAY800_COLOR wxColour(50, 58, 61)
|
||||
#define SEND_DESIGN_GRAY600_COLOR wxColour(144, 144, 144)
|
||||
#define SEND_DESIGN_GRAY400_COLOR wxColour(166, 169, 170)
|
||||
#define SEND_DESIGN_RESOUTION_PREFERENCES wxSize(FromDIP(540), -1)
|
||||
#define SEND_DESIGN_COMBOBOX_SIZE wxSize(FromDIP(140), -1)
|
||||
#define SEND_DESIGN_LARGE_COMBOBOX_SIZE wxSize(FromDIP(160), -1)
|
||||
#define SEND_DESIGN_INPUT_SIZE wxSize(FromDIP(50), -1)
|
||||
|
||||
|
||||
|
||||
|
||||
+8
-17
@@ -7,6 +7,7 @@
|
||||
#include "libslic3r/FilamentMixer.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/PublishSettings.hpp"
|
||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||
|
||||
#include "Search.hpp"
|
||||
@@ -5699,26 +5700,16 @@ if (is_marlin_flavor)
|
||||
optgroup->append_single_option_line("extruder_offset", "printer_extruder_basic_information#extruder-offset-position", extruder_idx);
|
||||
|
||||
//BBS: don't show retract related config menu in machine page
|
||||
// These optgroups are built from publishable_printer_retraction/z_hop_options() so the
|
||||
// published-3MF printer allowlist (their union in libslic3r/PublishSettings.hpp) can
|
||||
// never drift from what the machine page actually shows.
|
||||
optgroup = page->new_optgroup(L("Retraction"), L"param_retraction");
|
||||
optgroup->append_single_option_line("retraction_length", "printer_extruder_retraction#length", extruder_idx);
|
||||
optgroup->append_single_option_line("retract_restart_extra", "printer_extruder_retraction#extra-length-on-restart", extruder_idx);
|
||||
optgroup->append_single_option_line("retraction_speed", "printer_extruder_retraction#retraction-speed", extruder_idx);
|
||||
optgroup->append_single_option_line("deretraction_speed", "printer_extruder_retraction#deretraction-speed", extruder_idx);
|
||||
optgroup->append_single_option_line("retraction_minimum_travel", "printer_extruder_retraction#travel-distance-threshold", extruder_idx);
|
||||
optgroup->append_single_option_line("retract_when_changing_layer", "printer_extruder_retraction#retract-on-layer-change", extruder_idx);
|
||||
optgroup->append_single_option_line("wipe", "printer_extruder_retraction#wipe-while-retracting", extruder_idx);
|
||||
optgroup->append_single_option_line("wipe_distance", "printer_extruder_retraction#wipe-distance", extruder_idx);
|
||||
optgroup->append_single_option_line("retract_before_wipe", "printer_extruder_retraction#retract-amount-before-wipe", extruder_idx);
|
||||
// Orca
|
||||
optgroup->append_single_option_line("retract_after_wipe", "printer_extruder_retraction#retract-amount-after-wipe", extruder_idx);
|
||||
for (const PublishablePrinterOption& opt : publishable_printer_retraction_options())
|
||||
optgroup->append_single_option_line(opt.key, opt.icon, extruder_idx);
|
||||
|
||||
optgroup = page->new_optgroup(L("Z-Hop"), L"param_extruder_lift_enforcement");
|
||||
optgroup->append_single_option_line("retract_lift_enforce", "printer_extruder_z_hop#on-surfaces", extruder_idx);
|
||||
optgroup->append_single_option_line("z_hop_types", "printer_extruder_z_hop#z-hop-type", extruder_idx);
|
||||
optgroup->append_single_option_line("z_hop", "printer_extruder_z_hop#z-hop-height", extruder_idx);
|
||||
optgroup->append_single_option_line("travel_slope", "printer_extruder_z_hop#traveling-angle", extruder_idx);
|
||||
optgroup->append_single_option_line("retract_lift_above", "printer_extruder_z_hop#only-lift-z-above", extruder_idx);
|
||||
optgroup->append_single_option_line("retract_lift_below", "printer_extruder_z_hop#only-lift-z-below", extruder_idx);
|
||||
for (const PublishablePrinterOption& opt : publishable_printer_z_hop_options())
|
||||
optgroup->append_single_option_line(opt.key, opt.icon, extruder_idx);
|
||||
|
||||
optgroup = page->new_optgroup(L("Retraction when switching material"), L"param_retraction_material_change");
|
||||
optgroup->append_single_option_line("retract_length_toolchange", "printer_extruder_retraction#retraction-when-switching-materials", extruder_idx);
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
#include <memory>
|
||||
|
||||
//#include "BedShapeDialog.hpp"
|
||||
#include "Event.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "ConfigManipulation.hpp"
|
||||
#include "OptionsGroup.hpp"
|
||||
@@ -38,7 +37,6 @@
|
||||
//BBS: GUI refactor
|
||||
#include "Notebook.hpp"
|
||||
#include "ParamsPanel.hpp"
|
||||
#include "Widgets/RoundedRectangle.hpp"
|
||||
#include "Widgets/TextInput.hpp"
|
||||
#include "Widgets/CheckBox.hpp" // ORCA
|
||||
|
||||
@@ -472,6 +470,7 @@ protected:
|
||||
std::string m_last_sparse_infill_rotate_template_value;
|
||||
ConfigManipulation get_config_manipulation();
|
||||
friend class EditGCodeDialog;
|
||||
friend class PublishSettingsDialog;
|
||||
};
|
||||
|
||||
class TabPrint : public Tab
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Color.hpp"
|
||||
#include "format.hpp"
|
||||
#include "ConfigValueFormatter.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include "Tab.hpp"
|
||||
@@ -22,7 +23,6 @@
|
||||
#include "MsgDialog.hpp"
|
||||
|
||||
#include "PresetComboBoxes.hpp"
|
||||
#include "Widgets/RoundedRectangle.hpp"
|
||||
#include "Widgets/CheckBox.hpp"
|
||||
#include "Widgets/DialogButtons.hpp"
|
||||
#include "Widgets/HyperLink.hpp"
|
||||
@@ -571,14 +571,6 @@ void DiffModel::Clear()
|
||||
}
|
||||
|
||||
|
||||
static std::string get_pure_opt_key(std::string opt_key)
|
||||
{
|
||||
const int pos = opt_key.find("#");
|
||||
if (pos > 0)
|
||||
boost::erase_tail(opt_key, opt_key.size() - pos);
|
||||
return opt_key;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// DiffViewCtrl
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -1212,32 +1204,6 @@ bool UnsavedChangesDialog::save(PresetCollection* dependent_presets, bool show_s
|
||||
return true;
|
||||
}
|
||||
|
||||
wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConfig& config, bool is_infill = false, int idx = -1)
|
||||
{
|
||||
const ConfigOptionDef& def = config.def()->options.at(opt_key);
|
||||
const std::vector<std::string>& names = def.enum_labels;//ConfigOptionEnum<T>::get_enum_names();
|
||||
int val = 0;
|
||||
|
||||
if (idx >= 0)
|
||||
val = dynamic_cast<const ConfigOptionInts*>(config.option(opt_key))->get_at(idx);
|
||||
else
|
||||
val = config.option(opt_key)->getInt();
|
||||
|
||||
// Each infill doesn't use all list of infill declared in PrintConfig.hpp.
|
||||
// So we should "convert" val to the correct one
|
||||
if (is_infill) {
|
||||
for (auto key_val : *def.enum_keys_map)
|
||||
if (int(key_val.second) == val) {
|
||||
auto it = std::find(def.enum_values.begin(), def.enum_values.end(), key_val.first);
|
||||
if (it == def.enum_values.end())
|
||||
return "";
|
||||
return from_u8(_utf8(names[it - def.enum_values.begin()]));
|
||||
}
|
||||
return _L("Undefined");
|
||||
}
|
||||
return from_u8(_utf8(names[val]));
|
||||
}
|
||||
|
||||
// BBS
|
||||
#if 0
|
||||
static size_t get_id_from_opt_key(std::string opt_key)
|
||||
@@ -1251,194 +1217,6 @@ static size_t get_id_from_opt_key(std::string opt_key)
|
||||
}
|
||||
#endif
|
||||
|
||||
static wxString get_full_label(std::string opt_key, const DynamicPrintConfig& config)
|
||||
{
|
||||
opt_key = get_pure_opt_key(opt_key);
|
||||
auto option = config.option(opt_key);
|
||||
|
||||
if (!option || option->is_nil())
|
||||
return _L("N/A");
|
||||
|
||||
const ConfigOptionDef* opt = config.def()->get(opt_key);
|
||||
return opt->full_label.empty() ? opt->label : opt->full_label;
|
||||
}
|
||||
|
||||
static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& config)
|
||||
{
|
||||
int orig_opt_idx = -1;
|
||||
int opt_idx = -1;
|
||||
int pos = opt_key.find("#");
|
||||
std::string temp_str = opt_key;
|
||||
if (pos > 0) {
|
||||
boost::erase_head(temp_str, pos + 1);
|
||||
orig_opt_idx = static_cast<size_t>(atoi(temp_str.c_str()));
|
||||
}
|
||||
opt_idx = orig_opt_idx >= 0 ? orig_opt_idx : 0;
|
||||
opt_key = get_pure_opt_key(opt_key);
|
||||
auto option = config.option(opt_key);
|
||||
if (!option) {
|
||||
return _L("N/A");
|
||||
}
|
||||
auto opt_vector = dynamic_cast<const ConfigOptionVectorBase *>(option);
|
||||
|
||||
if ((option->is_scalar() && config.option(opt_key)->is_nil()) ||
|
||||
(option->is_vector() && opt_vector && opt_idx >= 0 && opt_idx < opt_vector->size() && opt_vector->is_nil(opt_idx)))
|
||||
return _L("N/A");
|
||||
|
||||
wxString out;
|
||||
|
||||
const ConfigOptionDef* opt = config.def()->get(opt_key);
|
||||
bool is_nullable = opt->nullable;
|
||||
|
||||
switch (opt->type) {
|
||||
case coInt:
|
||||
return from_u8((boost::format("%1%") % config.opt_int(opt_key)).str());
|
||||
case coInts: {
|
||||
if (is_nullable) {
|
||||
auto values = config.opt<ConfigOptionIntsNullable>(opt_key);
|
||||
if (opt_idx < values->size())
|
||||
return from_u8((boost::format("%1%") % values->get_at(opt_idx)).str());
|
||||
}
|
||||
else {
|
||||
auto values = config.opt<ConfigOptionInts>(opt_key);
|
||||
if (orig_opt_idx >= 0 && orig_opt_idx < values->size()) {
|
||||
return from_u8((boost::format("%1%") % values->get_at(opt_idx)).str());
|
||||
}
|
||||
else {
|
||||
std::string value_str;
|
||||
for (int i = 0; i < values->size(); i++) {
|
||||
value_str += std::to_string(values->get_at(i));
|
||||
if (i != values->size() - 1) {
|
||||
value_str += ",";
|
||||
}
|
||||
}
|
||||
return from_u8(value_str);
|
||||
}
|
||||
}
|
||||
return _L("Undefined");
|
||||
}
|
||||
case coBool:
|
||||
return config.opt_bool(opt_key) ? "true" : "false";
|
||||
case coBools: {
|
||||
if (is_nullable) {
|
||||
auto values = config.opt<ConfigOptionBoolsNullable>(opt_key);
|
||||
if (opt_idx < values->size())
|
||||
return values->get_at(opt_idx) ? "true" : "false";
|
||||
}
|
||||
else {
|
||||
auto values = config.opt<ConfigOptionBools>(opt_key);
|
||||
if (opt_idx < values->size())
|
||||
return values->get_at(opt_idx) ? "true" : "false";
|
||||
}
|
||||
return _L("Undefined");
|
||||
}
|
||||
case coPercent:
|
||||
return from_u8((boost::format("%1%%%") % int(config.optptr(opt_key)->getFloat())).str());
|
||||
case coPercents: {
|
||||
if (is_nullable) {
|
||||
auto values = config.opt<ConfigOptionPercentsNullable>(opt_key);
|
||||
if (opt_idx < values->size())
|
||||
return from_u8((boost::format("%1%%%") % values->get_at(opt_idx)).str());
|
||||
}
|
||||
else {
|
||||
auto values = config.opt<ConfigOptionPercents>(opt_key);
|
||||
if (opt_idx < values->size())
|
||||
return from_u8((boost::format("%1%%%") % values->get_at(opt_idx)).str());
|
||||
}
|
||||
return _L("Undefined");
|
||||
}
|
||||
case coFloat:
|
||||
return double_to_string(config.opt_float(opt_key));
|
||||
case coFloats: {
|
||||
if (is_nullable) {
|
||||
auto values = config.opt<ConfigOptionFloatsNullable>(opt_key);
|
||||
if (opt_idx < values->size())
|
||||
return double_to_string(values->get_at(opt_idx));
|
||||
}
|
||||
else {
|
||||
auto values = config.opt<ConfigOptionFloats>(opt_key);
|
||||
if (values && opt_idx < values->size())
|
||||
return double_to_string(values->get_at(opt_idx));
|
||||
}
|
||||
return _L("Undefined");
|
||||
}
|
||||
case coString:
|
||||
return from_u8(config.opt_string(opt_key));
|
||||
case coStrings: {
|
||||
const ConfigOptionStrings* strings = config.opt<ConfigOptionStrings>(opt_key);
|
||||
if (strings) {
|
||||
if (opt_key == "compatible_printers" || opt_key == "compatible_prints") {
|
||||
if (strings->empty())
|
||||
return _L("All");
|
||||
for (size_t id = 0; id < strings->size(); id++)
|
||||
out += from_u8(strings->get_at(id)) + "\n";
|
||||
out.RemoveLast(1);
|
||||
return out;
|
||||
}
|
||||
if (!strings->empty() && opt_idx < strings->values.size())
|
||||
return from_u8(strings->get_at(opt_idx));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case coFloatOrPercent: {
|
||||
const ConfigOptionFloatOrPercent* opt = config.opt<ConfigOptionFloatOrPercent>(opt_key);
|
||||
if (opt)
|
||||
out = double_to_string(opt->value) + (opt->percent ? "%" : "");
|
||||
return out;
|
||||
}
|
||||
case coEnum: {
|
||||
return get_string_from_enum(opt_key, config,
|
||||
opt_key == "top_surface_pattern" ||
|
||||
opt_key == "bottom_surface_pattern" ||
|
||||
opt_key == "internal_solid_infill_pattern" ||
|
||||
opt_key == "sparse_infill_pattern" ||
|
||||
opt_key == "ironing_pattern" ||
|
||||
opt_key == "support_ironing_pattern" ||
|
||||
opt_key == "support_pattern" ||
|
||||
opt_key == "support_interface_pattern")
|
||||
;
|
||||
}
|
||||
case coEnums: {
|
||||
return get_string_from_enum(opt_key, config,
|
||||
opt_key == "top_surface_pattern" ||
|
||||
opt_key == "bottom_surface_pattern" ||
|
||||
opt_key == "internal_solid_infill_pattern" ||
|
||||
opt_key == "sparse_infill_pattern" ||
|
||||
opt_key == "ironing_pattern" ||
|
||||
opt_key == "support_ironing_pattern" ||
|
||||
opt_key == "support_pattern" ||
|
||||
opt_key == "support_interface_pattern"
|
||||
, opt_idx);
|
||||
}
|
||||
case coPoint: {
|
||||
Vec2d val = config.opt<ConfigOptionPoint>(opt_key)->value;
|
||||
return from_u8((boost::format("[%1%]") % ConfigOptionPoint(val).serialize()).str());
|
||||
}
|
||||
case coPoints: {
|
||||
//BBS: add bed_exclude_area
|
||||
if (opt_key == "printable_area" || opt_key == "thumbnails") {
|
||||
ConfigOptionPoints points = *config.option<ConfigOptionPoints>(opt_key);
|
||||
//BuildVolume build_volume = {points.values, 0.};
|
||||
return get_thumbnails_string(points.values);
|
||||
}
|
||||
else if (opt_key == "bed_exclude_area") {
|
||||
return get_thumbnails_string(config.option<ConfigOptionPoints>(opt_key)->values);
|
||||
}
|
||||
else if (opt_key == "head_wrap_detect_zone") {
|
||||
return get_thumbnails_string(config.option<ConfigOptionPoints>(opt_key)->values);
|
||||
}
|
||||
else if (opt_key == "wrapping_exclude_area") {
|
||||
return get_thumbnails_string(config.option<ConfigOptionPoints>(opt_key)->values);
|
||||
}
|
||||
Vec2d val = config.opt<ConfigOptionPoints>(opt_key)->get_at(opt_idx);
|
||||
return from_u8((boost::format("[%1%]") % ConfigOptionPoint(val).serialize()).str());
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void UnsavedChangesDialog::update(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset, const wxString& header)
|
||||
{
|
||||
PresetCollection* presets = dependent_presets;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <wx/textdlg.h>
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/weakref.h>
|
||||
#include <wx/display.h>
|
||||
#include <wx/fileconf.h>
|
||||
#include <wx/file.h>
|
||||
@@ -562,6 +563,78 @@ void GuideFrame::OnScriptMessage(wxWebViewEvent &evt)
|
||||
m_ProfileJson["filament"][fName]["selected"] = 1;
|
||||
}
|
||||
}
|
||||
else if (strCmd == "check_for_new_printers") {
|
||||
json response = json::object();
|
||||
response["command"] = "check_new_printers_result";
|
||||
// Guide pages currently send sequence_id as a number, while older
|
||||
// pages may send it as a string. Preserve the value without
|
||||
// forcing either representation.
|
||||
if (j.contains("sequence_id"))
|
||||
response["sequence_id"] = j["sequence_id"];
|
||||
else
|
||||
response["sequence_id"] = "";
|
||||
|
||||
if (!m_MainPtr->preset_updater) {
|
||||
response["error"] = "Printer update service is unavailable.";
|
||||
wxString strJS = wxString::Format("HandleStudio(%s)", response.dump(-1, ' ', true));
|
||||
wxGetApp().CallAfter([this, strJS] { RunScript(strJS); });
|
||||
} else {
|
||||
// Orca: enumerate vendors directly from disk rather than from m_ProfileJson["model"]
|
||||
// — a vendor with no machine models (e.g. a filament-only bundle, or a test fixture
|
||||
// like "test123" with an empty machine_model_list) never gets a "vendor" entry
|
||||
// pushed into "model" by LoadProfileFamily(), so it would be invisible to the
|
||||
// request body and get endlessly re-offered by the server. Scan both the system dir
|
||||
// (already-installed vendors) and the bundled resources dir (shipped-but-not-yet-
|
||||
// installed vendors), same as LoadProfileData() does when building loaded_vendors.
|
||||
std::set<std::string> system_vendors;
|
||||
for (const auto& dir : {vendor_dir, rsrc_vendor_dir}) {
|
||||
if (!boost::filesystem::exists(dir))
|
||||
continue;
|
||||
for (const auto& entry : boost::filesystem::directory_iterator(dir)) {
|
||||
if (!boost::filesystem::is_directory(entry) && boost::iequals(entry.path().extension().string(), ".json"))
|
||||
system_vendors.insert(entry.path().stem().string());
|
||||
}
|
||||
}
|
||||
// Orca: check_new_vendors() is async (network + confirmation dialog + download
|
||||
// all happen off the calling thread apart from the dialog itself); guard against
|
||||
// this dialog being closed before the callback fires.
|
||||
wxWeakRef<GuideFrame> weak_this(this);
|
||||
try {
|
||||
m_MainPtr->preset_updater->check_new_vendors(
|
||||
system_vendors, [weak_this, response](std::vector<std::string> installed_vendors, bool declined) mutable {
|
||||
if (!weak_this)
|
||||
return;
|
||||
|
||||
// Orca: append the newly installed vendor(s) into the in-memory
|
||||
// profile data (instead of a full LoadProfileData() rescan of every
|
||||
// vendor) and push the refreshed list to the webview, the same way
|
||||
// request_userguide_profile does, so the printer list picks them up
|
||||
// without needing to reopen the guide.
|
||||
for (const auto& vendor_id : installed_vendors) {
|
||||
weak_this->LoadProfileFamily(vendor_id, (weak_this->vendor_dir / (vendor_id + ".json")).string());
|
||||
}
|
||||
if (!installed_vendors.empty()) {
|
||||
json profile_response = json::object();
|
||||
profile_response["command"] = "response_userguide_profile";
|
||||
profile_response["sequence_id"] = "10001";
|
||||
profile_response["response"] = weak_this->m_ProfileJson;
|
||||
wxString profileJS = wxString::Format("HandleStudio(%s)", profile_response.dump(-1, ' ', true));
|
||||
weak_this->RunScript(profileJS);
|
||||
}
|
||||
|
||||
response["vendors"] = installed_vendors;
|
||||
response["declined"] = declined;
|
||||
wxString strJS = wxString::Format("HandleStudio(%s)", response.dump(-1, ' ', true));
|
||||
weak_this->RunScript(strJS);
|
||||
});
|
||||
} catch (const std::exception &e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Failed to check for new printers: " << e.what();
|
||||
response["error"] = "Failed to check for new printers.";
|
||||
wxString strJS = wxString::Format("HandleStudio(%s)", response.dump(-1, ' ', true));
|
||||
wxGetApp().CallAfter([this, strJS] { RunScript(strJS); });
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (strCmd == "user_guide_finish") {
|
||||
SaveProfile();
|
||||
|
||||
@@ -1644,13 +1717,15 @@ int GuideFrame::LoadProfileFamily(std::string strVendor, std::string strFilePath
|
||||
OneModel["materials"] = pm["default_materials"];
|
||||
|
||||
// wxString strCoverPath = wxString::Format("%s\\%s\\%s_cover.png", strFolder, strVendor, std::string(s1.mb_str()));
|
||||
std::string cover_file = s1 + "_cover.png";
|
||||
boost::filesystem::path cover_path = boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "/profiles/" / strVendor / cover_file).make_preferred();
|
||||
std::string cover_file = s1 + "_cover.png";
|
||||
boost::filesystem::path cover_path = boost::filesystem::absolute(vendor_dir / cover_file).make_preferred();
|
||||
BOOST_LOG_TRIVIAL(info) << "[WebGuideDialog] " << cover_path;
|
||||
if (!boost::filesystem::exists(cover_path)) {
|
||||
cover_path =
|
||||
(boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "/web/image/printer/") /
|
||||
cover_file)
|
||||
.make_preferred();
|
||||
cover_path = boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "/profiles/" / strVendor / cover_file)
|
||||
.make_preferred();
|
||||
if (!boost::filesystem::exists(cover_path))
|
||||
cover_path = (boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "/web/image/printer/") / cover_file)
|
||||
.make_preferred();
|
||||
}
|
||||
OneModel["cover"] = cover_path.string();
|
||||
|
||||
|
||||
@@ -162,6 +162,15 @@ void Button::SetCenter(bool isCenter)
|
||||
{
|
||||
this->isCenter = isCenter; }
|
||||
|
||||
void Button::SetIndicator(bool on)
|
||||
{
|
||||
if (m_show_indicator == on)
|
||||
return;
|
||||
m_show_indicator = on;
|
||||
messureSize();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void Button::SetVertical(bool vertical)
|
||||
{
|
||||
this->vertical = vertical;
|
||||
@@ -324,6 +333,13 @@ void Button::render(wxDC& dc)
|
||||
szContent.x -= d;
|
||||
}
|
||||
}
|
||||
if (m_show_indicator) {
|
||||
const int dot = FromDIP(6);
|
||||
if (vertical)
|
||||
szContent.y += dot + FromDIP(6);
|
||||
else
|
||||
szContent.x += dot + FromDIP(6);
|
||||
}
|
||||
// move to center
|
||||
wxRect rcContent = { {0, 0}, size };
|
||||
if (isCenter) {
|
||||
@@ -364,6 +380,17 @@ void Button::render(wxDC& dc)
|
||||
#endif
|
||||
dc.DrawText(text, pt);
|
||||
}
|
||||
if (m_show_indicator) {
|
||||
const int dot = FromDIP(6); // diameter
|
||||
wxPoint dot_pt;
|
||||
dot_pt.x = pt.x + (text.IsEmpty() ? 0 : textSize.x) + FromDIP(6) + dot / 2;
|
||||
// Centre on the content vertically; a bitmap-only (empty-label) tab has no text row.
|
||||
dot_pt.y = text.IsEmpty() ? rcContent.y + rcContent.height / 2 : pt.y + textSize.y / 2;
|
||||
const wxColour c = StateColor::darkModeColorFor(m_indicator_color);
|
||||
dc.SetBrush(wxBrush(c));
|
||||
dc.SetPen(wxPen(c));
|
||||
dc.DrawCircle(dot_pt, dot / 2);
|
||||
}
|
||||
}
|
||||
|
||||
void Button::messureSize()
|
||||
@@ -388,6 +415,14 @@ void Button::messureSize()
|
||||
if (szIcon.y > szContent.y) szContent.y = szIcon.y;
|
||||
}
|
||||
}
|
||||
if (m_show_indicator) {
|
||||
// Indicator dot sits to the right of the label: its diameter plus the gap from the text.
|
||||
const int dot = FromDIP(6);
|
||||
if (vertical)
|
||||
szContent.y += dot + FromDIP(6);
|
||||
else
|
||||
szContent.x += dot + FromDIP(6);
|
||||
}
|
||||
wxSize size = szContent + paddingSize * 2;
|
||||
if (minSize.GetHeight() > 0)
|
||||
size.SetHeight(minSize.GetHeight());
|
||||
|
||||
@@ -4,28 +4,30 @@
|
||||
#include "../wxExtensions.hpp"
|
||||
#include "StaticBox.hpp"
|
||||
#include <wx/tipwin.h>
|
||||
#include <wx/colour.h>
|
||||
|
||||
class ButtonProps
|
||||
{
|
||||
public:
|
||||
static int ChoiceButtonGap(){return 10;};
|
||||
static int WindowButtonGap(){return 10;};
|
||||
static int ChoiceButtonGap() { return 10; };
|
||||
static int WindowButtonGap() { return 10; };
|
||||
};
|
||||
|
||||
enum class ButtonStyle{
|
||||
enum class ButtonStyle {
|
||||
Regular,
|
||||
Confirm,
|
||||
Alert,
|
||||
Disabled,
|
||||
};
|
||||
|
||||
enum class ButtonType{
|
||||
Compact , // Font10 FullyRounded For spaces with less areas
|
||||
Window , // Font12 FullyRounded For regular buttons in windows and not related with parameter boxes
|
||||
Choice , // Font14 Semi-Rounded For dialog/window choice buttons
|
||||
enum class ButtonType {
|
||||
Compact, // Font10 FullyRounded For spaces with less areas
|
||||
Window, // Font12 FullyRounded For regular buttons in windows and not related with parameter boxes
|
||||
Choice, // Font14 Semi-Rounded For dialog/window choice buttons
|
||||
Parameter, // Font14 Semi-Rounded For buttons that near parameter boxes
|
||||
Icon , // ------ Semi-Rounded For buttons that only has icons. icons should be 16x16 and iconSize has to be defined as 16 while creation of button
|
||||
Expanded , // Font14 Semi-Rounded For full length buttons. ex. buttons in static box
|
||||
Icon, // ------ Semi-Rounded For buttons that only has icons. icons should be 16x16 and iconSize has to be defined as 16 while
|
||||
// creation of button
|
||||
Expanded, // Font14 Semi-Rounded For full length buttons. ex. buttons in static box
|
||||
};
|
||||
|
||||
class Button : public StaticBox
|
||||
@@ -36,15 +38,17 @@ class Button : public StaticBox
|
||||
wxSize paddingSize;
|
||||
ScalableBitmap active_icon;
|
||||
|
||||
StateColor text_color;
|
||||
StateColor text_color;
|
||||
|
||||
bool pressedDown = false;
|
||||
bool m_selected = true;
|
||||
bool canFocus = true;
|
||||
bool canFocus = true;
|
||||
bool isCenter = true;
|
||||
bool vertical = false;
|
||||
bool m_show_indicator = false;
|
||||
wxColour m_indicator_color = wxColour("#009688");
|
||||
|
||||
static const int buttonWidth = 200;
|
||||
static const int buttonWidth = 200;
|
||||
static const int buttonHeight = 50;
|
||||
|
||||
public:
|
||||
@@ -68,19 +72,23 @@ public:
|
||||
|
||||
void SetStyle(const ButtonStyle style /*= ButtonStyle::Regular*/, const ButtonType type /*= ButtonType::None*/);
|
||||
|
||||
void SetTextColor(StateColor const &color);
|
||||
void SetTextColor(StateColor const& color);
|
||||
|
||||
void SetTextColorNormal(wxColor const &color);
|
||||
void SetTextColorNormal(wxColor const& color);
|
||||
|
||||
void SetSelected(bool selected = true) { m_selected = selected; }
|
||||
|
||||
// Show a small coloured dot to the right of the label (used by TabCtrl tabs to flag that
|
||||
// the tab's category has a selected/toggled setting).
|
||||
void SetIndicator(bool on);
|
||||
|
||||
// Only meant to be used by inspector, not public API
|
||||
ButtonStyle GetStyle() const { return m_style; }
|
||||
ButtonType GetType() const { return m_type; }
|
||||
bool IsSelected() const { return m_selected; }
|
||||
ButtonType GetType() const { return m_type; }
|
||||
bool IsSelected() const { return m_selected; }
|
||||
|
||||
bool Enable(bool enable = true) override;
|
||||
void EnableTooltipEvenDisabled();// The tip will be shown even if the button is disabled
|
||||
void EnableTooltipEvenDisabled(); // The tip will be shown even if the button is disabled
|
||||
|
||||
void SetCanFocus(bool canFocus) override;
|
||||
|
||||
@@ -104,7 +112,7 @@ protected:
|
||||
private:
|
||||
bool m_has_style = false;
|
||||
ButtonStyle m_style;
|
||||
ButtonType m_type;
|
||||
ButtonType m_type;
|
||||
|
||||
void paintEvent(wxPaintEvent& evt);
|
||||
|
||||
@@ -115,10 +123,10 @@ private:
|
||||
// some useful events
|
||||
void mouseDown(wxMouseEvent& event);
|
||||
void mouseReleased(wxMouseEvent& event);
|
||||
void mouseCaptureLost(wxMouseCaptureLostEvent &event);
|
||||
void keyDownUp(wxKeyEvent &event);
|
||||
void mouseCaptureLost(wxMouseCaptureLostEvent& event);
|
||||
void keyDownUp(wxKeyEvent& event);
|
||||
|
||||
//
|
||||
//
|
||||
void sendButtonEvent();
|
||||
|
||||
// parent motion
|
||||
|
||||
@@ -7,6 +7,7 @@ BEGIN_EVENT_TABLE(StaticBox, wxWindow)
|
||||
|
||||
// catch paint events
|
||||
//EVT_ERASE_BACKGROUND(StaticBox::eraseEvent)
|
||||
EVT_SIZE(StaticBox::sizeEvent)
|
||||
EVT_PAINT(StaticBox::paintEvent)
|
||||
|
||||
END_EVENT_TABLE()
|
||||
@@ -140,6 +141,12 @@ void StaticBox::eraseEvent(wxEraseEvent& evt)
|
||||
#endif
|
||||
}
|
||||
|
||||
void StaticBox::sizeEvent(wxSizeEvent& evt)
|
||||
{
|
||||
Refresh();
|
||||
evt.Skip();
|
||||
}
|
||||
|
||||
void StaticBox::paintEvent(wxPaintEvent& evt)
|
||||
{
|
||||
// depending on your system you may need to look at double-buffered dcs
|
||||
|
||||
@@ -46,6 +46,8 @@ public:
|
||||
protected:
|
||||
void eraseEvent(wxEraseEvent& evt);
|
||||
|
||||
void sizeEvent(wxSizeEvent& evt);
|
||||
|
||||
void paintEvent(wxPaintEvent& evt);
|
||||
|
||||
void render(wxDC& dc);
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
#include <wx/dc.h>
|
||||
|
||||
wxDEFINE_EVENT( wxEVT_TAB_SEL_CHANGING, wxCommandEvent );
|
||||
wxDEFINE_EVENT( wxEVT_TAB_SEL_CHANGED, wxCommandEvent );
|
||||
wxDEFINE_EVENT(wxEVT_TAB_SEL_CHANGING, wxCommandEvent);
|
||||
wxDEFINE_EVENT(wxEVT_TAB_SEL_CHANGED, wxCommandEvent);
|
||||
|
||||
BEGIN_EVENT_TABLE(TabCtrl, StaticBox)
|
||||
|
||||
@@ -22,11 +22,7 @@ END_EVENT_TABLE()
|
||||
#define TAB_BUTTON_PADDING_Y 2
|
||||
#define TAB_BUTTON_PADDING TAB_BUTTON_PADDING_X, TAB_BUTTON_PADDING_Y
|
||||
|
||||
TabCtrl::TabCtrl(wxWindow * parent,
|
||||
wxWindowID id,
|
||||
const wxPoint & pos,
|
||||
const wxSize & size,
|
||||
long style)
|
||||
TabCtrl::TabCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
|
||||
: StaticBox(parent, id, pos, size, style)
|
||||
{
|
||||
#if 0
|
||||
@@ -42,14 +38,11 @@ TabCtrl::TabCtrl(wxWindow * parent,
|
||||
hsizer->Add(sizer, 0, wxEXPAND | wxBOTTOM, border_width * 4);
|
||||
SetSizer(hsizer);
|
||||
Bind(wxEVT_COMMAND_BUTTON_CLICKED, &TabCtrl::buttonClicked, this);
|
||||
//wxString reason;
|
||||
//IsTransparentBackgroundSupported(&reason);
|
||||
// wxString reason;
|
||||
// IsTransparentBackgroundSupported(&reason);
|
||||
}
|
||||
|
||||
TabCtrl::~TabCtrl()
|
||||
{
|
||||
delete images;
|
||||
}
|
||||
TabCtrl::~TabCtrl() { delete images; }
|
||||
|
||||
int TabCtrl::GetSelection() const { return sel; }
|
||||
|
||||
@@ -75,15 +68,13 @@ void TabCtrl::SelectItem(int item)
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void TabCtrl::Unselect()
|
||||
{
|
||||
SelectItem(-1);
|
||||
}
|
||||
void TabCtrl::Unselect() { SelectItem(-1); }
|
||||
|
||||
void TabCtrl::Rescale()
|
||||
{
|
||||
for (auto & b : btns)
|
||||
for (auto& b : btns)
|
||||
b->Rescale();
|
||||
relayout();
|
||||
}
|
||||
|
||||
bool TabCtrl::SetFont(wxFont const& font)
|
||||
@@ -95,28 +86,32 @@ bool TabCtrl::SetFont(wxFont const& font)
|
||||
return true;
|
||||
}
|
||||
|
||||
int TabCtrl::AppendItem(const wxString &item,
|
||||
int image, int selImage,
|
||||
void * clientData)
|
||||
int TabCtrl::AppendItem(const wxString& item, int image, int selImage, void* clientData)
|
||||
{
|
||||
Button * btn = new Button();
|
||||
Button* btn = new Button();
|
||||
btn->Create(this, item, "", wxBORDER_NONE);
|
||||
btn->SetFont(GetFont());
|
||||
btn->SetTextColor(StateColor(
|
||||
std::make_pair(0x6B6B6C, (int) StateColor::NotChecked),
|
||||
std::make_pair(*wxLIGHT_GREY, (int) StateColor::Normal)));
|
||||
btn->SetTextColor(
|
||||
StateColor(std::make_pair(0x6B6B6C, (int) StateColor::NotChecked), std::make_pair(wxColour("#262E30"), (int) StateColor::Normal)));
|
||||
btn->SetBackgroundColor(StateColor());
|
||||
btn->SetCornerRadius(0);
|
||||
btn->SetPaddingSize({TAB_BUTTON_PADDING});
|
||||
btns.push_back(btn);
|
||||
if (btns.size() > 1)
|
||||
sizer->GetItem(sizer->GetItemCount() - 1)->SetMinSize({0, 0});
|
||||
sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, TAB_BUTTON_SPACE);
|
||||
sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, item_space);
|
||||
sizer->AddStretchSpacer(1);
|
||||
relayout();
|
||||
return btns.size() - 1;
|
||||
}
|
||||
|
||||
int TabCtrl::AppendItem(const wxString& item, const wxBitmap& bitmap, void* clientData)
|
||||
{
|
||||
const int index = AppendItem(item, -1, -1, clientData);
|
||||
SetItemBitmap(index, bitmap);
|
||||
return index;
|
||||
}
|
||||
|
||||
bool TabCtrl::DeleteItem(int item)
|
||||
{
|
||||
if (item < 0 || item >= btns.size()) {
|
||||
@@ -136,7 +131,7 @@ bool TabCtrl::DeleteItem(int item)
|
||||
sizer->GetItem(sizer->GetItemCount() - 1)->SetMinSize({0, 0});
|
||||
|
||||
if (selection_changed) {
|
||||
sel--; // `relayout()` uses `sel` so we need to update this before calling `relayout()`
|
||||
sel--; // `relayout()` uses `sel` so we need to update this before calling `relayout()`
|
||||
}
|
||||
relayout();
|
||||
if (selection_changed) {
|
||||
@@ -159,75 +154,87 @@ void TabCtrl::DeleteAllItems()
|
||||
|
||||
unsigned int TabCtrl::GetCount() const { return btns.size(); }
|
||||
|
||||
wxString TabCtrl::GetItemText(unsigned int item) const
|
||||
wxString TabCtrl::GetItemText(unsigned int item) const { return item < btns.size() ? btns[item]->GetLabel() : wxString{}; }
|
||||
|
||||
void TabCtrl::SetItemText(unsigned int item, wxString const& value)
|
||||
{
|
||||
return item < btns.size() ? btns[item]->GetLabel() : wxString{};
|
||||
if (item >= btns.size())
|
||||
return;
|
||||
btns[item]->SetLabel(value);
|
||||
}
|
||||
|
||||
void TabCtrl::SetItemText(unsigned int item, wxString const &value)
|
||||
void TabCtrl::SetItemBitmap(unsigned int item, const wxBitmap& bitmap)
|
||||
{
|
||||
if (item >= btns.size()) return;
|
||||
btns[item]->SetLabel(value);
|
||||
if (item >= btns.size())
|
||||
return;
|
||||
btns[item]->SetIcon(bitmap);
|
||||
relayout();
|
||||
}
|
||||
|
||||
void TabCtrl::SetItemIndicator(unsigned int item, bool on)
|
||||
{
|
||||
if (item >= btns.size())
|
||||
return;
|
||||
btns[item]->SetIndicator(on);
|
||||
relayout();
|
||||
}
|
||||
|
||||
bool TabCtrl::GetItemBold(unsigned int item) const
|
||||
{
|
||||
if (item >= btns.size()) return false;
|
||||
if (item >= btns.size())
|
||||
return false;
|
||||
return btns[item]->GetFont() == bold;
|
||||
}
|
||||
|
||||
void TabCtrl::SetItemBold(unsigned int item, bool bold)
|
||||
{
|
||||
if (item >= btns.size()) return;
|
||||
if (item >= btns.size())
|
||||
return;
|
||||
btns[item]->SetFont(bold ? this->bold : GetFont());
|
||||
btns[item]->Rescale();
|
||||
}
|
||||
|
||||
void* TabCtrl::GetItemData(unsigned int item) const
|
||||
{
|
||||
if (item >= btns.size()) return nullptr;
|
||||
if (item >= btns.size())
|
||||
return nullptr;
|
||||
return btns[item]->GetClientData();
|
||||
}
|
||||
|
||||
void TabCtrl::SetItemData(unsigned int item, void* clientData)
|
||||
{
|
||||
if (item >= btns.size()) return;
|
||||
if (item >= btns.size())
|
||||
return;
|
||||
btns[item]->SetClientData(clientData);
|
||||
}
|
||||
|
||||
void TabCtrl::AssignImageList(wxImageList* imageList)
|
||||
{
|
||||
if (images == imageList) return;
|
||||
if (images == imageList)
|
||||
return;
|
||||
delete images;
|
||||
images = imageList;
|
||||
}
|
||||
|
||||
void TabCtrl::SetItemTextColour(unsigned int item, const StateColor &col)
|
||||
void TabCtrl::SetItemTextColour(unsigned int item, const StateColor& col)
|
||||
{
|
||||
if (item >= btns.size()) return;
|
||||
if (item >= btns.size())
|
||||
return;
|
||||
btns[item]->SetTextColor(col);
|
||||
}
|
||||
|
||||
int TabCtrl::GetFirstVisibleItem() const
|
||||
{
|
||||
return btns.size() == 0 ? -1 : 0;
|
||||
}
|
||||
int TabCtrl::GetFirstVisibleItem() const { return btns.size() == 0 ? -1 : 0; }
|
||||
|
||||
int TabCtrl::GetNextVisible(int item) const
|
||||
{
|
||||
return ++item < btns.size() ? item : -1;
|
||||
}
|
||||
int TabCtrl::GetNextVisible(int item) const { return ++item < btns.size() ? item : -1; }
|
||||
|
||||
bool TabCtrl::IsVisible(unsigned int item) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool TabCtrl::IsVisible(unsigned int item) const { return true; }
|
||||
|
||||
void TabCtrl::DoSetSize(int x, int y, int width, int height, int sizeFlags)
|
||||
{
|
||||
auto size = GetSize();
|
||||
wxWindow::DoSetSize(x, y, width, height, sizeFlags);
|
||||
if (size == GetSize()) return;
|
||||
if (size == GetSize())
|
||||
return;
|
||||
relayout();
|
||||
}
|
||||
|
||||
@@ -235,7 +242,9 @@ void TabCtrl::DoSetSize(int x, int y, int width, int height, int sizeFlags)
|
||||
|
||||
WXLRESULT TabCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
|
||||
{
|
||||
if (nMsg == WM_GETDLGCODE) { return DLGC_WANTARROWS; }
|
||||
if (nMsg == WM_GETDLGCODE) {
|
||||
return DLGC_WANTARROWS;
|
||||
}
|
||||
return wxWindow::MSWWindowProc(nMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
@@ -244,15 +253,15 @@ WXLRESULT TabCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
|
||||
void TabCtrl::relayout()
|
||||
{
|
||||
int offset = 10;
|
||||
int item = sel + 1;
|
||||
int first = 0;
|
||||
int item = sel + 1;
|
||||
int first = 0;
|
||||
for (int i = 0; i < item; ++i)
|
||||
offset += btns[i]->GetMinSize().x + TAB_BUTTON_SPACE * 2;
|
||||
offset += btns[i]->GetMinSize().x + item_space * 2;
|
||||
if (item < btns.size())
|
||||
offset += btns[item]->GetMinSize().x + TAB_BUTTON_SPACE * 2;
|
||||
int width = GetSize().x;
|
||||
offset += btns[item]->GetMinSize().x + item_space * 2;
|
||||
int width = GetSize().x;
|
||||
for (int i = 0; i < btns.size(); ++i) {
|
||||
auto size = btns[i]->GetMinSize().x + TAB_BUTTON_SPACE * 2;
|
||||
auto size = btns[i]->GetMinSize().x + item_space * 2;
|
||||
if (i < sel && offset > width) {
|
||||
sizer->Show(i * 2 + 1, false);
|
||||
sizer->Show(i * 2 + 2, false);
|
||||
@@ -273,14 +282,32 @@ void TabCtrl::relayout()
|
||||
sizer->GetItem(i * 2 + 2)->SetMinSize({0, 0});
|
||||
}
|
||||
if (item >= btns.size())
|
||||
-- item;
|
||||
--item;
|
||||
// Keep spacing 2 ~ 10 TAB_BUTTON_SPACE
|
||||
int b = GetSize().x - offset - 10 - (item + 1 - first) * TAB_BUTTON_SPACE * 8;
|
||||
int b = GetSize().x - offset - 10 - (item + 1 - first) * item_space * 8;
|
||||
sizer->GetItem(item * 2 + 2)->SetMinSize({b > 0 ? b : 0, 0});
|
||||
Layout();
|
||||
}
|
||||
|
||||
void TabCtrl::buttonClicked(wxCommandEvent &event)
|
||||
void TabCtrl::SetItemSpace(int space)
|
||||
{
|
||||
if (space < 0 || space == item_space)
|
||||
return;
|
||||
item_space = space;
|
||||
relayout();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
int TabCtrl::GetFullSize() const
|
||||
{
|
||||
// Mirrors relayout(): a 10px leading spacer plus every button's min width and spacing.
|
||||
int width = 10;
|
||||
for (const Button* btn : btns)
|
||||
width += btn->GetMinSize().x + item_space * 2;
|
||||
return width;
|
||||
}
|
||||
|
||||
void TabCtrl::buttonClicked(wxCommandEvent& event)
|
||||
{
|
||||
SetFocus();
|
||||
auto btn = event.GetEventObject();
|
||||
@@ -288,7 +315,7 @@ void TabCtrl::buttonClicked(wxCommandEvent &event)
|
||||
SelectItem(iter == btns.end() ? -1 : iter - btns.begin());
|
||||
}
|
||||
|
||||
void TabCtrl::keyDown(wxKeyEvent &event)
|
||||
void TabCtrl::keyDown(wxKeyEvent& event)
|
||||
{
|
||||
switch (event.GetKeyCode()) {
|
||||
case WXK_UP:
|
||||
@@ -307,11 +334,13 @@ void TabCtrl::keyDown(wxKeyEvent &event)
|
||||
void TabCtrl::doRender(wxDC& dc)
|
||||
{
|
||||
wxSize size = GetSize();
|
||||
int states = state_handler.states();
|
||||
if (sel < 0) { return; }
|
||||
int states = state_handler.states();
|
||||
if (sel < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto x1 = btns[sel]->GetPosition().x;
|
||||
auto x2 = x1 + btns[sel]->GetSize().x;
|
||||
auto x1 = btns[sel]->GetPosition().x;
|
||||
auto x2 = x1 + btns[sel]->GetSize().x;
|
||||
const int BS2 = (1 + border_width) / 2;
|
||||
#if 0
|
||||
const int BS = border_width / 2;
|
||||
|
||||
@@ -3,32 +3,30 @@
|
||||
|
||||
#include "Button.hpp"
|
||||
|
||||
wxDECLARE_EVENT( wxEVT_TAB_SEL_CHANGING, wxCommandEvent );
|
||||
wxDECLARE_EVENT( wxEVT_TAB_SEL_CHANGED, wxCommandEvent );
|
||||
wxDECLARE_EVENT(wxEVT_TAB_SEL_CHANGING, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_TAB_SEL_CHANGED, wxCommandEvent);
|
||||
|
||||
class TabCtrl : public StaticBox
|
||||
{
|
||||
std::vector<Button*> btns;
|
||||
wxImageList* images = nullptr;
|
||||
wxBoxSizer * sizer = nullptr;
|
||||
wxBoxSizer* sizer = nullptr;
|
||||
|
||||
int sel = -1;
|
||||
wxFont bold;
|
||||
int item_space = 2; // space around each button, both sides (SetItemSpace)
|
||||
|
||||
public:
|
||||
TabCtrl(wxWindow * parent,
|
||||
wxWindowID id,
|
||||
const wxPoint & pos = wxDefaultPosition,
|
||||
const wxSize & size = wxDefaultSize,
|
||||
long style = 0);
|
||||
TabCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = 0);
|
||||
|
||||
~TabCtrl();
|
||||
|
||||
public:
|
||||
virtual bool SetFont(wxFont const & font) override;
|
||||
virtual bool SetFont(wxFont const& font) override;
|
||||
|
||||
public:
|
||||
int AppendItem(const wxString &item, int image = -1, int selImage = -1, void *clientData = nullptr);
|
||||
int AppendItem(const wxString& item, int image = -1, int selImage = -1, void* clientData = nullptr);
|
||||
int AppendItem(const wxString& item, const wxBitmap& bitmap, void* clientData = nullptr);
|
||||
|
||||
bool DeleteItem(int item);
|
||||
|
||||
@@ -36,7 +34,7 @@ public:
|
||||
|
||||
unsigned int GetCount() const;
|
||||
|
||||
int GetSelection() const;
|
||||
int GetSelection() const;
|
||||
|
||||
void SelectItem(int item);
|
||||
|
||||
@@ -45,15 +43,19 @@ public:
|
||||
virtual void Rescale();
|
||||
|
||||
wxString GetItemText(unsigned int item) const;
|
||||
void SetItemText(unsigned int item, wxString const &value);
|
||||
void SetItemText(unsigned int item, wxString const& value);
|
||||
void SetItemBitmap(unsigned int item, const wxBitmap& bitmap);
|
||||
|
||||
bool GetItemBold(unsigned int item) const;
|
||||
void SetItemBold(unsigned int item, bool bold);
|
||||
// Show/hide the small "has selection" dot next to a tab's text.
|
||||
void SetItemIndicator(unsigned int item, bool on);
|
||||
|
||||
void* GetItemData(unsigned int item) const;
|
||||
void SetItemData(unsigned int item, void *clientData);
|
||||
|
||||
void AssignImageList(wxImageList *imageList);
|
||||
bool GetItemBold(unsigned int item) const;
|
||||
void SetItemBold(unsigned int item, bool bold);
|
||||
|
||||
void* GetItemData(unsigned int item) const;
|
||||
void SetItemData(unsigned int item, void* clientData);
|
||||
|
||||
void AssignImageList(wxImageList* imageList);
|
||||
|
||||
void SetItemTextColour(unsigned int item, const StateColor& col);
|
||||
|
||||
@@ -62,6 +64,12 @@ public:
|
||||
int GetNextVisible(int item) const;
|
||||
bool IsVisible(unsigned int item) const;
|
||||
|
||||
// Extra space around each tab button (in px on both sides). Defaults to the control-wide
|
||||
// standard; call before appending items so every button picks it up.
|
||||
void SetItemSpace(int space);
|
||||
|
||||
int GetFullSize() const;
|
||||
|
||||
private:
|
||||
virtual void DoSetSize(int x, int y, int width, int height, int sizeFlags = wxSIZE_AUTO) override;
|
||||
|
||||
@@ -71,10 +79,10 @@ private:
|
||||
|
||||
void relayout();
|
||||
|
||||
void buttonClicked(wxCommandEvent & event);
|
||||
void keyDown(wxKeyEvent &event);
|
||||
void buttonClicked(wxCommandEvent& event);
|
||||
void keyDown(wxKeyEvent& event);
|
||||
|
||||
void doRender(wxDC & dc) override;
|
||||
void doRender(wxDC& dc) override;
|
||||
|
||||
// some useful events
|
||||
bool sendTabCtrlEvent(bool changing = false);
|
||||
|
||||
Reference in New Issue
Block a user