Merge remote-tracking branch 'upstream/main' into merge-test-upstream

# Conflicts:
#	src/slic3r/GUI/GCodeViewer.cpp
#	src/slic3r/GUI/GLCanvas3D.hpp
#	src/slic3r/GUI/Tab.cpp
This commit is contained in:
Clifford Garwood
2026-05-25 03:11:50 -04:00
681 changed files with 22580 additions and 8953 deletions
+21 -3
View File
@@ -23,6 +23,7 @@
#include <cstring>
#include <iostream>
#include <math.h>
#include <csignal>
#if defined(__linux__) || defined(__LINUX__)
#include <condition_variable>
@@ -4456,7 +4457,7 @@ int CLI::run(int argc, char **argv)
size_t num_objects = model.objects.size();
for (size_t i = 0; i < num_objects; ++ i) {
ModelObjectPtrs new_objects;
model.objects.front()->split(&new_objects);
model.objects.front()->split(&new_objects, false); // TODO: add cli option to enable this?
model.delete_object(size_t(0));
}
}
@@ -6435,6 +6436,7 @@ int CLI::run(int argc, char **argv)
glfwGetVersion(&gl_major, &gl_minor, &gl_verbos);
BOOST_LOG_TRIVIAL(info) << boost::format("opengl version %1%.%2%.%3%")%gl_major %gl_minor %gl_verbos;
bool thumbnail_opengl_ready = false;
glfwSetErrorCallback(glfw_callback);
int ret = glfwInit();
if (ret == GLFW_FALSE) {
@@ -6463,13 +6465,22 @@ int CLI::run(int argc, char **argv)
GLFWwindow* window = glfwCreateWindow(640, 480, "base_window", NULL, NULL);
if (window == NULL)
{
BOOST_LOG_TRIVIAL(error) << "Failed to create GLFW window" << std::endl;
BOOST_LOG_TRIVIAL(error) << "Failed to create GLFW window; skipping thumbnail rendering for CLI export" << std::endl;
}
else
else {
glfwMakeContextCurrent(window);
thumbnail_opengl_ready = true;
}
}
//opengl manager related logic
if (!thumbnail_opengl_ready) {
BOOST_LOG_TRIVIAL(error) << "OpenGL context unavailable; skip thumbnail generating" << std::endl;
need_create_thumbnail_group = false;
need_create_no_light_group = false;
need_create_top_group = false;
}
else
{
GUI::OpenGLManager opengl_mgr;
bool opengl_valid = opengl_mgr.init_gl(false);
@@ -7449,6 +7460,13 @@ extern "C" {
#else /* _MSC_VER */
int main(int argc, char **argv)
{
#ifndef _WIN32
// Ignore SIGPIPE so a write to a closed socket (e.g. a dropped printer
// network connection) returns EPIPE to the caller instead of terminating
// the whole process. Without this, losing the printer link kills
// OrcaSlicer with SIGPIPE (exit 141) and produces no crash report.
std::signal(SIGPIPE, SIG_IGN);
#endif
return CLI().run(argc, argv);
}
#endif /* _MSC_VER */
+31 -2
View File
@@ -212,8 +212,14 @@ void AppConfig::set_defaults()
if (get("camera_navigation_style").empty())
set("camera_navigation_style", "0");
if (get("swap_mouse_buttons").empty())
set_bool("swap_mouse_buttons", false);
if (get("left_mouse_drag_action").empty())
set("left_mouse_drag_action", "2");
if (get("middle_mouse_drag_action").empty())
set("middle_mouse_drag_action", "1");
if (get("right_mouse_drag_action").empty())
set("right_mouse_drag_action", "1");
if (get("reverse_mouse_wheel_zoom").empty())
set_bool("reverse_mouse_wheel_zoom", false);
@@ -230,6 +236,29 @@ void AppConfig::set_defaults()
if (get("camera_orbit_mult").empty())
set("camera_orbit_mult", "1.0");
if (get(SETTING_OPENGL_AA_SAMPLES).empty())
set(SETTING_OPENGL_AA_SAMPLES, "4");
if (get(SETTING_OPENGL_FXAA_ENABLED).empty())
set_bool(SETTING_OPENGL_FXAA_ENABLED, false);
if (get(SETTING_OPENGL_FPS_CAP).empty())
set(SETTING_OPENGL_FPS_CAP, "0");
else {
int fps_cap = 0;
try {
fps_cap = std::stoi(get(SETTING_OPENGL_FPS_CAP));
}
catch (...) {
fps_cap = 0;
}
fps_cap = std::max(0, std::min(fps_cap, 240));
set(SETTING_OPENGL_FPS_CAP, std::to_string(fps_cap));
}
if (get(SETTING_OPENGL_SHOW_FPS_OVERLAY).empty())
set_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY, false);
if (get("export_sources_full_pathnames").empty())
set_bool("export_sources_full_pathnames", false);
+4
View File
@@ -30,6 +30,10 @@ using namespace nlohmann;
#define SETTING_NETWORK_PLUGIN_REMIND_LATER "network_plugin_remind_later"
#define SETTING_USE_ENCRYPTED_TOKEN_FILE "use_encrypted_token_file"
#define SETTING_CLOUD_PROVIDERS "cloud_providers"
#define SETTING_OPENGL_AA_SAMPLES "opengl_antialiasing_samples"
#define SETTING_OPENGL_FXAA_ENABLED "opengl_fxaa_enabled"
#define SETTING_OPENGL_FPS_CAP "opengl_fps_cap"
#define SETTING_OPENGL_SHOW_FPS_OVERLAY "opengl_show_fps_overlay"
#if defined(_WIN32) || defined(_WIN64)
#define BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.09"
+167 -51
View File
@@ -63,7 +63,7 @@ static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelV
vol->cut_info = src_volume->cut_info;
}
static void process_volume_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
static void process_volume_cut( const ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh)
{
const auto volume_matrix = volume->get_matrix();
@@ -178,7 +178,7 @@ static void process_modifier_cut(ModelVolume* volume, const Transform3d& instanc
lower->add_volume(*volume);
}
static void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
static void process_solid_part_cut(const ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
{
// Perform cut
@@ -279,9 +279,22 @@ void Cut::post_process(ModelObject* upper, ModelObject* lower, ModelObjectPtrs&
}
void Cut::finalize(const ModelObjectPtrs& objects)
void Cut::finalize(const ModelObjectPtrs& objects, const std::vector<std::optional<TriangleSelector::SavedPainting>>& saved_paintings)
{
//clear model from temporarry objects
// Paint volumes
for (const auto& saved_painting : saved_paintings) {
if (saved_painting) {
for (const auto object : objects) {
for (const auto volume : object->volumes) {
if (volume->is_model_part() && !volume->is_cut_connector()) {
volume->restore_painting(saved_painting, true);
}
}
}
}
}
//clear model from temporary objects
m_model.clear_objects();
// add to model result objects
@@ -320,7 +333,17 @@ const ModelObjectPtrs& Cut::perform_with_plane()
const Transformation cut_transformation = Transformation(m_cut_matrix);
const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset());
std::vector<std::optional<TriangleSelector::SavedPainting>> saved_paintings;
for (ModelVolume* volume : mo->volumes) {
// Save painting data before reset_extra_facets() discards it.
if (m_attributes.has(ModelObjectCutAttribute::KeepPaint)) {
saved_paintings.emplace_back(volume->save_painting());
if (saved_paintings.back()) {
// Transform mesh to cut space (same transform as process_volume_cut applies)
saved_paintings.back()->mesh.transform(instance_matrix * volume->get_matrix(), true);
}
}
volume->reset_extra_facets();
if (!volume->is_model_part()) {
@@ -376,9 +399,9 @@ const ModelObjectPtrs& Cut::perform_with_plane()
}
}
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end";
finalize(cut_object_ptrs, saved_paintings);
finalize(cut_object_ptrs);
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end";
return m_model.objects;
}
@@ -431,7 +454,7 @@ static void merge_solid_parts_inside_object(ModelObjectPtrs& objects)
}
const ModelObjectPtrs& Cut::perform_by_contour(std::vector<Part> parts, int dowels_count)
const ModelObjectPtrs& Cut::perform_by_contour(const ModelObject* src_object, std::vector<Part> parts, int dowels_count)
{
ModelObject* cut_mo = m_model.objects.front();
@@ -446,6 +469,19 @@ const ModelObjectPtrs& Cut::perform_by_contour(std::vector<Part> parts, int dowe
lower->name = lower->name + "_B";
}
// Save painting data so we later can remap it.
std::vector<std::optional<TriangleSelector::SavedPainting>> saved_paintings;
if (m_attributes.has(ModelObjectCutAttribute::KeepPaint)) {
const auto instance_matrix = src_object->instances[m_instance]->get_transformation().get_matrix_no_offset();
for (const auto volume : src_object->volumes) {
saved_paintings.emplace_back(volume->save_painting());
if (saved_paintings.back()) {
// Transform mesh to cut space (same transform as process_volume_cut applies)
saved_paintings.back()->mesh.transform(instance_matrix * volume->get_matrix(), true);
}
}
}
const size_t cut_parts_cnt = parts.size();
bool has_modifiers = false;
@@ -475,7 +511,7 @@ const ModelObjectPtrs& Cut::perform_by_contour(std::vector<Part> parts, int dowe
merge_solid_parts_inside_object(cut_object_ptrs);
// replace initial objects in model with cut object
finalize(cut_object_ptrs);
finalize(cut_object_ptrs, saved_paintings);
}
else if (volumes.size() > cut_parts_cnt) {
// Means that object is cut with connectors
@@ -506,7 +542,7 @@ const ModelObjectPtrs& Cut::perform_by_contour(std::vector<Part> parts, int dowe
merge_solid_parts_inside_object(cut_object_ptrs);
// replace initial objects in model with cut object
finalize(cut_object_ptrs);
finalize(cut_object_ptrs, saved_paintings);
// Add Dowel-connectors as separate objects to model
if (cut_connectors_obj.size() >= 3)
@@ -518,7 +554,12 @@ const ModelObjectPtrs& Cut::perform_by_contour(std::vector<Part> parts, int dowe
}
const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts/* = false*/)
const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove,
const Transform3d& rotation_m,
const int groove_count,
const float groove_gap,
const float m_radius,
bool keep_as_parts /* = false*/)
{
ModelObject* cut_mo = m_model.objects.front();
@@ -532,6 +573,20 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran
upper->name = upper->name + "_A";
lower->name = lower->name + "_B";
}
// Save painting data so we later can remap it.
std::vector<std::optional<TriangleSelector::SavedPainting>> saved_paintings;
if (m_attributes.has(ModelObjectCutAttribute::KeepPaint)) {
const auto instance_matrix = cut_mo->instances[m_instance]->get_transformation().get_matrix_no_offset();
for (const auto volume : cut_mo->volumes) {
saved_paintings.emplace_back(volume->save_painting());
if (saved_paintings.back()) {
// Transform mesh to cut space (same transform as process_volume_cut applies)
saved_paintings.back()->mesh.transform(instance_matrix * volume->get_matrix(), true);
}
}
}
const double groove_half_depth = 0.5 * double(groove.depth);
Model tmp_model_for_cut = Model();
@@ -565,61 +620,108 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran
reset_instance_transformation(object, m_instance);
};
// cut by upper plane
const Transform3d cut_matrix_upper = translation_transform(rotation_m * (groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix;
// cut by upper plane (+Z)
{
const Transform3d cut_matrix_upper = translation_transform(rotation_m * (groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix;
cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
}
// cut by lower plane
const Transform3d cut_matrix_lower = translation_transform(rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix;
// cut by lower plane (-Z)
{
const Transform3d cut_matrix_lower = translation_transform(rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix;
cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
}
// cut middle part with 2 angles and add parts to related upper/lower objects
// Compute same slot outer width used in preview plane
const float groove_width = calculate_groove_width(groove, m_radius);
const double h_side_shift = 0.5 * double(groove.width + groove.depth / tan(groove.flaps_angle));
ModelObject* groove_object{nullptr};
// cut by angle1 plane
{
const Transform3d cut_matrix_angle1 = translation_transform(rotation_m * (-h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle));
// multiple cuts
for (int i = 0; i < groove_count; i++) {
bool is_first_groove = i == 0;
bool is_last_groove = i == groove_count - 1;
cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
// Calculate the x-axis offset for this dovetail
float groove_offset_factor_start = -.5 * ((groove_count - 1));
float groove_offset_factor = groove_offset_factor_start + i;
float offset_x = groove_offset_factor * (groove_gap + groove_width);
tmp_object->clone_for_cut(&groove_object);
for (ModelVolume* volume : tmp_object->volumes) {
ModelVolume* new_vol = groove_object->add_volume(*volume);
new_vol->reset_from_upper();
}
// isolate area of current groove
if (!is_first_groove) {
float left_cut_position = (-groove_gap / 2.f) - (groove_width / 2.f) + offset_x;
const Transform3d cut_matrix_left = translation_transform(rotation_m * (left_cut_position * Vec3d::UnitX())) *
m_cut_matrix * rotation_transform(Vec3d(0, M_PI / 2.0, 0));
cut(groove_object, cut_matrix_left, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
}
if (!is_last_groove) {
float right_cut_position = (groove_gap / 2.f) + (groove_width / 2.f) + offset_x;
const Transform3d cut_matrix_right = translation_transform(rotation_m * (right_cut_position * Vec3d::UnitX())) *
m_cut_matrix * rotation_transform(Vec3d(0, M_PI / 2.0, 0));
cut(groove_object, cut_matrix_right, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
}
const Transform3d groove_translation = translation_transform(rotation_m * (offset_x * Vec3d::UnitX()));
// cut middle part with 2 angles and add parts to related upper/lower objects
const double h_side_shift = 0.5 * double(groove.width + groove.depth / tan(groove.flaps_angle));
// cut by angle1 plane
{
const Transform3d cut_matrix_angle1 = groove_translation * translation_transform(rotation_m * (-h_side_shift * Vec3d::UnitX())) *
m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle));
cut(groove_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
}
// cut by angle2 plane
{
const Transform3d cut_matrix_angle2 = groove_translation * translation_transform(rotation_m * (h_side_shift * Vec3d::UnitX())) *
m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle));
cut(groove_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
}
// apply tolerance to the middle part
{
const double h_groove_shift_tolerance = groove_half_depth - (double)groove.depth_tolerance;
const Transform3d cut_matrix_lower_tolerance = groove_translation * translation_transform(rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) *
m_cut_matrix;
cut(groove_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
const double h_side_shift_tolerance = h_side_shift - 0.5 * double(groove.width_tolerance);
const Transform3d cut_matrix_angle1_tolerance = groove_translation * translation_transform(rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) *
m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle));
cut(groove_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
const Transform3d cut_matrix_angle2_tolerance = groove_translation * translation_transform(rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) *
m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle));
cut(groove_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
}
add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
groove_object->clear_volumes();
}
// cut by angle2 plane
{
const Transform3d cut_matrix_angle2 = translation_transform(rotation_m * (h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle));
cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
}
// apply tolerance to the middle part
{
const double h_groove_shift_tolerance = groove_half_depth - (double)groove.depth_tolerance;
const Transform3d cut_matrix_lower_tolerance = translation_transform(rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * m_cut_matrix;
cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
const double h_side_shift_tolerance = h_side_shift - 0.5 * double(groove.width_tolerance);
const Transform3d cut_matrix_angle1_tolerance = translation_transform(rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle));
cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
const Transform3d cut_matrix_angle2_tolerance = translation_transform(rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle));
cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
}
// this part can be added to the upper object now
add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
ModelObjectPtrs cut_object_ptrs;
if (keep_as_parts) {
@@ -658,10 +760,24 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran
merge_solid_parts_inside_object(cut_object_ptrs);
}
finalize(cut_object_ptrs);
finalize(cut_object_ptrs, saved_paintings);
return m_model.objects;
}
float Cut::calculate_groove_width (const Cut::Groove& groove, const float m_radius)
{
// Compute same slot outer width used in preview plane
const double flap_width = is_approx(groove.flaps_angle, 0.f) ? groove.depth : groove.depth / sin(groove.flaps_angle);
const double total_flap_width = 2.0 * flap_width * cos(groove.flaps_angle);
const double slot_neck_half_width = 0.5f * (groove.width);
const double slot_mouth_half_width = 0.5 * (groove.width + total_flap_width);
const double plane_half_height = 0.5f* (1.5f * (1.5f *m_radius));
const double flap_taper_offset = plane_half_height * tan(groove.angle);
const double slot_outer_x_max = std::max(slot_mouth_half_width + flap_taper_offset, slot_neck_half_width + flap_taper_offset);
return float(2.0 * slot_outer_x_max);
}
} // namespace Slic3r
+11 -5
View File
@@ -11,7 +11,7 @@ namespace Slic3r {
using ModelObjectPtrs = std::vector<ModelObject*>;
enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, KeepAsParts, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, InvalidateCutInfo };
enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, KeepAsParts, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, InvalidateCutInfo, KeepPaint };
using ModelObjectCutAttributes = enum_bitmask<ModelObjectCutAttribute>;
ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute);
@@ -25,7 +25,7 @@ class Cut {
void post_process(ModelObject* object, ModelObjectPtrs& objects, bool keep, bool place_on_cut, bool flip);
void post_process(ModelObject* upper_object, ModelObject* lower_object, ModelObjectPtrs& objects);
void finalize(const ModelObjectPtrs& objects);
void finalize(const ModelObjectPtrs& objects, const std::vector<std::optional<TriangleSelector::SavedPainting>>& saved_paintings);
public:
@@ -56,10 +56,16 @@ public:
};
const ModelObjectPtrs& perform_with_plane();
const ModelObjectPtrs& perform_by_contour(std::vector<Part> parts, int dowels_count);
const ModelObjectPtrs& perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts = false);
const ModelObjectPtrs& perform_by_contour(const ModelObject* src_object, std::vector<Part> parts, int dowels_count);
const ModelObjectPtrs& perform_with_groove(const Groove& groove,
const Transform3d& rotation_m,
const int groove_count,
const float groove_gap,
const float m_radius,
bool keep_as_parts = false);
}; // namespace Cut
static float calculate_groove_width(const Cut::Groove& groove, const float m_radius);
}; // namespace Cut
} // namespace Slic3r
+14 -1
View File
@@ -271,6 +271,9 @@ struct SurfaceFillParams
// Params for Lateral honeycomb
float infill_overhang_angle = 60.f;
// For Gyroid: when true, use the parameterized "optimized" wave.
bool gyroid_optimized = false;
bool operator<(const SurfaceFillParams &rhs) const {
#define RETURN_COMPARE_NON_EQUAL(KEY) if (this->KEY < rhs.KEY) return true; if (this->KEY > rhs.KEY) return false;
#define RETURN_COMPARE_NON_EQUAL_TYPED(TYPE, KEY) if (TYPE(this->KEY) < TYPE(rhs.KEY)) return true; if (TYPE(this->KEY) > TYPE(rhs.KEY)) return false;
@@ -303,6 +306,7 @@ struct SurfaceFillParams
RETURN_COMPARE_NON_EQUAL(symmetric_infill_y_axis);
RETURN_COMPARE_NON_EQUAL(infill_lock_depth);
RETURN_COMPARE_NON_EQUAL(skin_infill_depth); RETURN_COMPARE_NON_EQUAL(infill_overhang_angle);
RETURN_COMPARE_NON_EQUAL(gyroid_optimized);
return false;
}
@@ -330,7 +334,8 @@ struct SurfaceFillParams
this->lateral_lattice_angle_2 == rhs.lateral_lattice_angle_2 &&
this->infill_lock_depth == rhs.infill_lock_depth &&
this->skin_infill_depth == rhs.skin_infill_depth &&
this->infill_overhang_angle == rhs.infill_overhang_angle;
this->infill_overhang_angle == rhs.infill_overhang_angle &&
this->gyroid_optimized == rhs.gyroid_optimized;
}
};
@@ -920,6 +925,12 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
// Orca: apply fill multiline only for sparse infill
params.multiline = params.extrusion_role == erInternalInfill ? int(region_config.fill_multiline) : 1;
// Pass through gyroid_optimized only when the effective pattern is Gyroid,
// so non-Gyroid fills do not differ in SurfaceFillParams by an irrelevant flag
// (which would unnecessarily split fill batching).
// Stored on SurfaceFillParams; copied to FillParams during conversion.
params.gyroid_optimized = (params.pattern == ipGyroid) && region_config.gyroid_optimized;
if (params.extrusion_role == erInternalInfill) {
params.angle = calculate_infill_rotation_angle(layer.object(), layer.id(), region_config.infill_direction.value,
region_config.sparse_infill_rotate_template.value);
@@ -1273,6 +1284,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
params.lateral_lattice_angle_1 = surface_fill.params.lateral_lattice_angle_1;
params.lateral_lattice_angle_2 = surface_fill.params.lateral_lattice_angle_2;
params.infill_overhang_angle = surface_fill.params.infill_overhang_angle;
params.gyroid_optimized = surface_fill.params.gyroid_optimized;
// BBS
params.flow = surface_fill.params.flow;
@@ -1468,6 +1480,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc
params.lateral_lattice_angle_2 = surface_fill.params.lateral_lattice_angle_2;
params.infill_overhang_angle = surface_fill.params.infill_overhang_angle;
params.multiline = surface_fill.params.multiline;
params.gyroid_optimized = surface_fill.params.gyroid_optimized;
for (ExPolygon &expoly : surface_fill.expolygons) {
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
+3
View File
@@ -79,6 +79,9 @@ struct FillParams
// Layer height for Concentric infill with Arachne.
coordf_t layer_height { 0.f };
// For Gyroid: when true, use the parameterized "optimized" variant.
bool gyroid_optimized { false };
// For Lateral lattice
coordf_t lateral_lattice_angle_1 { 0.f };
coordf_t lateral_lattice_angle_2 { 0.f };
+171 -9
View File
@@ -1,4 +1,5 @@
#include "../ClipperUtils.hpp"
#include "../MarchingSquares.hpp"
#include "../ShortestPath.hpp"
#include "../Surface.hpp"
#include <cmath>
@@ -7,6 +8,99 @@
#include "FillBase.hpp"
#include "FillGyroid.hpp"
// ---------------------------------------------------------------------------
// Marching-squares scalar field for the optimized gyroid branch.
// Modeled after FillTpmsFK.cpp's ScalarField.
//
// The gyroid scalar field is the standard implicit equation
// F(x,y,z) = sin(fx*x)cos(fy*y) + sin(fy*y)cos(fz*z) + sin(fz*z)cos(fx*x)
// Marching squares extracts the iso-zero contour, which gives smoother
// transitions between vertical and horizontal regimes than the analytical
// asin-based wave generator. Setting fz = omega * baseline anisotropically
// tightens the wave along the layer-stacking axis, shortening the effective
// vertical strand length and improving column-buckling resistance under
// Z-axis compression.
// ---------------------------------------------------------------------------
namespace marchsq {
using namespace Slic3r;
using coordr_t = long;
using Pointf = Vec2d;
struct GyroidField
{
static constexpr float gsizef = 0.40f;
static constexpr float rsizef = 0.004f;
const coord_t rsize = scaled(rsizef);
const coordr_t gsize = std::round(gsizef / rsizef);
Point size;
Point offs;
coordf_t z;
float fx;
float fy;
float fz;
float isoval = 0.0f;
explicit GyroidField(const BoundingBox bb, const coordf_t z, const float period, const float omega = 1.0f)
: size{bb.size()}, offs{bb.min}, z{z}
{
const float baseline = float(2.0 * PI) / std::max(period, 1e-3f);
fx = baseline;
fy = baseline;
fz = omega * baseline;
}
float get_scalar(coordf_t x, coordf_t y, coordf_t z_arg) const
{
const float a = fx * float(x);
const float b = fy * float(y);
const float c = fz * float(z_arg);
return std::sin(a) * std::cos(b) + std::sin(b) * std::cos(c) + std::sin(c) * std::cos(a);
}
float get_scalar(Coord p) const
{
Pointf pf = to_Pointf(p);
return get_scalar(pf.x(), pf.y(), z);
}
inline coord_t to_coord (const coordr_t& x) const { return x * rsize; }
inline coordr_t to_coordr(const coord_t& x) const { return x / rsize; }
inline Point to_Point (const Coord& p) const { return Point(to_coord(p.c) + offs.x(), to_coord(p.r) + offs.y()); }
inline Coord to_Coord (const Point& p) const { return Coord(to_coordr(p.y() - offs.y()), to_coordr(p.x() - offs.x())); }
inline Pointf to_Pointf(const Point& p) const { return Pointf(unscaled(p.x()), unscaled(p.y())); }
inline Pointf to_Pointf(const Coord& p) const { return to_Pointf(to_Point(p)); }
};
template<> struct _RasterTraits<GyroidField>
{
using ValueType = float;
static float get (const GyroidField& sf, size_t row, size_t col) { return sf.get_scalar(Coord(row, col)); }
static size_t rows(const GyroidField& sf) { return sf.to_coordr(sf.size.y()); }
static size_t cols(const GyroidField& sf) { return sf.to_coordr(sf.size.x()); }
};
inline Polylines get_gyroid_polylines(const GyroidField& sf, const double tolerance = SCALED_EPSILON)
{
std::vector<Ring> rings = execute_with_policy(ex_tbb, sf, sf.isoval, {sf.gsize, sf.gsize});
Polylines polys;
polys.reserve(rings.size());
for (const Ring& ring : rings) {
Polyline poly;
Points& pts = poly.points;
pts.reserve(ring.size() + 1);
for (const Coord& crd : ring)
pts.emplace_back(sf.to_Point(crd));
pts.push_back(pts.front());
if (tolerance >= 0.0)
poly.simplify(tolerance);
polys.emplace_back(poly);
}
return polys;
}
} // namespace marchsq
namespace Slic3r {
static inline double f(double x, double z_sin, double z_cos, bool vertical, bool flip)
@@ -102,6 +196,49 @@ static std::vector<Vec2d> make_one_period(double width, double scaleFactor, doub
return points;
}
// ---------------------------------------------------------------------------
// "Optimized" gyroid wave: marching-squares variant gated on
// params.gyroid_optimized. The wave shape is extracted from the gyroid
// implicit scalar field (see marchsq::GyroidField above) at iso=0, with
// the Z dimension's spatial frequency multiplied by an Euler-Bernoulli
// buckling-derived factor so the vertical strands become shorter columns,
// raising the critical buckling load against Z-axis compression.
//
// The formula is INVERTED from a naive "scale with density" derivation:
// at LOW density the gyroid strands are long and slender (prime buckling
// targets), so they need the most shortening; at high density the strands
// are already short and need little extra help. omega is therefore the
// inverse-square-root of density_adjusted:
//
// omega = sqrt(1 / density_adj) / sqrt(1 + layer_h/spacing),
// clamped [1.0, 2.0]
//
// fx and fy are left at the baseline frequency, so the per-XY-slice line
// length per unit area is preserved -> mass at the same `sparse_infill_density`
// setting matches the standard gyroid path. Strength gain comes purely from
// the shorter vertical column length (P_cr proportional to 1/L^2).
//
// Empirical Python sim (sim_gyroid_compare.py) at layer_h=0.20, spacing=0.45:
//
// density omega line/std strength/std strength_per_mass
// 10% 2.00 1.00 2.84 2.84
// 15% 1.38 1.00 1.89 1.89
// 20% 1.19 1.00 1.42 1.42
// 30% 1.00 1.00 1.00 1.00
// 50%+ 1.00 1.00 1.00 1.00
//
// When gyroid_optimized is false, behavior is byte-identical to the
// standard parametric gyroid path below.
// ---------------------------------------------------------------------------
static inline double compute_omega_factor(double density_adjusted, double line_spacing, double layer_height)
{
double lh_ratio = (line_spacing > 0.) ? layer_height / line_spacing : 0.5;
double correction = 1.0 / std::sqrt(1.0 + lh_ratio);
double raw = std::sqrt(1.0 / std::max(density_adjusted, 0.1)) * correction;
return std::clamp(raw, 1.0, 2.0);
}
static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height)
{
const double scaleFactor = scale_(line_spacing) / density_adjusted;
@@ -173,16 +310,41 @@ void FillGyroid::_fill_surface_single(
bb.offset(expand);
// generate pattern
Polylines polylines = make_gyroid_waves(
scale_(this->z),
density_adjusted,
this->spacing,
ceil(bb.size()(0) / distance) + 1.,
ceil(bb.size()(1) / distance) + 1.);
Polylines polylines;
if (params.gyroid_optimized) {
// Marching-squares path on the gyroid implicit field. Base period matches
// the standard parametric path's wavelength: 2*pi * spacing / density_adj.
// omega >= 1 always, so fz >= baseline -> shorter vertical wavelength ->
// shorter effective column length -> higher buckling resistance.
//
// Mass: fx and fy are left at baseline (same as standard), so the
// per-XY-slice line length per unit area is approximately preserved.
// Empirically (sim_gyroid_compare.py) the optimized line/std ratio is
// ~1.000 across densities, so no period compensation is needed.
const double lh = (params.layer_height > 0.) ? double(params.layer_height) : double(this->spacing);
const double omega = compute_omega_factor(density_adjusted, this->spacing * params.multiline, lh);
// shift the polyline to the grid origin
for (Polyline &pl : polylines)
pl.translate(bb.min);
const float density_factor = std::max(0.001f, float(params.density * DensityAdjust / params.multiline));
const float period = float(2.0 * M_PI) * float(this->spacing) / density_factor;
// bb is already expanded above by 10 * scale_(spacing) for edge artifacts;
// skip a second offset here to avoid raster-area bloat in the marching squares pass.
marchsq::GyroidField sf(bb, this->z, period, float(omega));
polylines = marchsq::get_gyroid_polylines(sf, SCALED_SPARSE_INFILL_RESOLUTION);
} else {
polylines = make_gyroid_waves(
scale_(this->z),
density_adjusted,
this->spacing,
ceil(bb.size()(0) / distance) + 1.,
ceil(bb.size()(1) / distance) + 1.);
// The parametric generator produces wave coords relative to the grid origin;
// shift them into absolute layer coords. The marching-squares branch above
// already emits absolute coords via GyroidField::to_Point, so it skips this.
for (Polyline &pl : polylines)
pl.translate(bb.min);
}
// Apply multiline offset if needed
multiline_fill(polylines, params, spacing);
+15 -1
View File
@@ -1163,10 +1163,15 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
const bool is_ramming = (gcodegen.config().single_extruder_multi_material) ||
(!gcodegen.config().single_extruder_multi_material &&
gcodegen.config().filament_multitool_ramming.get_at(tcr.initial_tool));
// Orca: user-facing override (Printer Settings > Wipe tower > "Tool change on wipe tower").
// Forces the toolhead to travel over the wipe tower before issuing Tx even on multi-toolhead
// printers without ramming, where Orca would otherwise emit Tx in place (potentially over the part).
const bool tool_change_on_wipe_tower = gcodegen.config().tool_change_on_wipe_tower.value;
const bool should_travel_to_tower = !tcr.priming && (tcr.force_travel // wipe tower says so
|| !needs_toolchange // this is just finishing the tower with no toolchange
|| will_go_down // Make sure to move to prime tower before moving down
|| is_ramming);
|| is_ramming
|| tool_change_on_wipe_tower);
if (should_travel_to_tower || gcodegen.m_need_change_layer_lift_z) {
// FIXME: It would be better if the wipe tower set the force_travel flag for all toolchanges,
@@ -5887,6 +5892,9 @@ std::string GCode::extrude_loop(const ExtrusionLoop& loop_ref,
// if polyline was shorter than the clipping distance we'd get a null polyline, so
// we discard it in that case
const double seam_gap = scale_(m_config.seam_gap.get_abs_value(nozzle_diameter));
const bool seam_gap_applied = enable_seam_slope || m_enable_loop_clipping;
const double seam_gap_distance_mm = seam_gap_applied ? unscale_(seam_gap) : 0.0;
double seam_scarf_distance_mm = 0.0;
const double clip_length = m_enable_loop_clipping && !enable_seam_slope ? seam_gap : 0;
// get paths
@@ -6035,6 +6043,7 @@ std::string GCode::extrude_loop(const ExtrusionLoop& loop_ref,
const double slope_min_length = slope_entire_loop ? loop_length : std::min(m_config.seam_slope_min_length.value, loop_length);
const int slope_steps = m_config.seam_slope_steps;
const double slope_max_segment_length = scale_(slope_min_length / slope_steps);
seam_scarf_distance_mm = slope_min_length;
// Calculate the sloped loop
ExtrusionLoopSloped new_loop(paths, seam_gap, slope_min_length, slope_max_segment_length, start_slope_ratio, loop.loop_role());
@@ -6059,6 +6068,11 @@ std::string GCode::extrude_loop(const ExtrusionLoop& loop_ref,
}
}
if (description == "perimeter") {
m_processor.result().print_statistics.total_seam_gap_distance += static_cast<float>(seam_gap_distance_mm);
m_processor.result().print_statistics.total_seam_scarf_distance += static_cast<float>(seam_scarf_distance_mm);
}
// BBS
if (m_wipe.enable && FILAMENT_CONFIG(wipe)) {
m_wipe.path = Polyline();
+16 -13
View File
@@ -719,6 +719,9 @@ std::string CoolingBuffer::apply_layer_cooldown(
// Second generate the adjusted G-code.
std::string new_gcode;
new_gcode.reserve(gcode.size() * 2);
// ORCA: per-printer minimum non-zero fan PWM. Applied at every part-cooling fan emission below so any
// non-zero command is raised to at least this percent (0 disables the clamp).
const unsigned int part_cooling_fan_min_pwm = static_cast<unsigned int>(std::max(0, m_config.part_cooling_fan_min_pwm.value));
bool overhang_fan_control= false;
int overhang_fan_speed = 0;
bool internal_bridge_fan_control= false; // ORCA: Add support for separate internal bridge fan speed control
@@ -727,7 +730,7 @@ std::string CoolingBuffer::apply_layer_cooldown(
int supp_interface_fan_speed = 0;
bool ironing_fan_control= false; // ORCA: Add support for ironing fan speed control
int ironing_fan_speed = 0; // ORCA: Add support for ironing fan speed control
auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode,
auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, part_cooling_fan_min_pwm,
&overhang_fan_control, &overhang_fan_speed,
&internal_bridge_fan_control, &internal_bridge_fan_speed,
&supp_interface_fan_control, &supp_interface_fan_speed,
@@ -743,11 +746,11 @@ std::string CoolingBuffer::apply_layer_cooldown(
int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer);
supp_interface_fan_speed = EXTRUDER_CONFIG(support_material_interface_fan_speed);
if (close_fan_the_first_x_layers <= 0 && full_fan_speed_layer > 0) {
// When ramping up fan speed from close_fan_the_first_x_layers to full_fan_speed_layer, force close_fan_the_first_x_layers above zero,
// so there will be a zero fan speed at least at the 1st layer.
close_fan_the_first_x_layers = 1;
}
// ORCA: previously a silent override forced `close_fan_the_first_x_layers` from 0 up to 1 whenever a ramp
// was configured (`full_fan_speed_layer > 0`), so the first printed layer always had the fan disabled.
// That hid the user's literal "no cooling for the first 0 layers" setting and produced a non-zero starting
// factor on the ramp denominator. The override has been removed: with N=0 and M>0 the ramp now genuinely
// starts on layer 0 at a factor of 1/M and reaches 100% at layer M-1, matching the intent of the option.
if (int(layer_id) >= close_fan_the_first_x_layers) {
float fan_max_speed = EXTRUDER_CONFIG(fan_max_speed);
float slow_down_layer_time = float(EXTRUDER_CONFIG(slow_down_layer_time));
@@ -806,7 +809,7 @@ std::string CoolingBuffer::apply_layer_cooldown(
m_fan_speed = fan_speed_new;
m_current_fan_speed = fan_speed_new;
if (immediately_apply)
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed);
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed, part_cooling_fan_min_pwm);
}
//BBS
if (additional_fan_speed_new != m_additional_fan_speed) {
@@ -980,26 +983,26 @@ std::string CoolingBuffer::apply_layer_cooldown(
if (need_set_fan) {
if (fan_speed_change_requests[CoolingLine::TYPE_OVERHANG_FAN_START]){
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, overhang_fan_speed);
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, overhang_fan_speed, part_cooling_fan_min_pwm);
m_current_fan_speed = overhang_fan_speed;
} else if (fan_speed_change_requests[CoolingLine::TYPE_INTERNAL_BRIDGE_FAN_START]){ // ORCA: Add support for separate internal bridge fan speed control
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, internal_bridge_fan_speed);
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, internal_bridge_fan_speed, part_cooling_fan_min_pwm);
m_current_fan_speed = internal_bridge_fan_speed;
}
else if (fan_speed_change_requests[CoolingLine::TYPE_SUPPORT_INTERFACE_FAN_START]){
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, supp_interface_fan_speed);
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, supp_interface_fan_speed, part_cooling_fan_min_pwm);
m_current_fan_speed = supp_interface_fan_speed;
}
else if (fan_speed_change_requests[CoolingLine::TYPE_IRONING_FAN_START]){
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, ironing_fan_speed);
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, ironing_fan_speed, part_cooling_fan_min_pwm);
m_current_fan_speed = ironing_fan_speed;
}
else if(fan_speed_change_requests[CoolingLine::TYPE_FORCE_RESUME_FAN] && m_current_fan_speed != -1){
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed);
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed, part_cooling_fan_min_pwm);
fan_speed_change_requests[CoolingLine::TYPE_FORCE_RESUME_FAN] = false;
}
else
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed);
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed, part_cooling_fan_min_pwm);
need_set_fan = false;
}
pos = line_end;
+4 -1
View File
@@ -230,7 +230,10 @@ void FanMover::_remove_slow_fan(int16_t min_speed, float past_sec) {
std::string FanMover::_set_fan(int16_t speed) {
//const Tool* tool = m_writer.get_tool(m_currrent_extruder < 20 ? m_currrent_extruder : 0);
return GCodeWriter::set_fan(m_writer.config.gcode_flavor.value, speed);
// ORCA: apply the per-printer non-zero fan PWM floor so reposted fan commands respect the clamp too.
const int floor_pct = m_writer.config.part_cooling_fan_min_pwm.value;
const unsigned int part_cooling_fan_min_pwm = floor_pct > 0 ? static_cast<unsigned int>(floor_pct) : 0u;
return GCodeWriter::set_fan(m_writer.config.gcode_flavor.value, speed, part_cooling_fan_min_pwm);
}
+76 -21
View File
@@ -1230,8 +1230,15 @@ void GCodeProcessor::run_post_process()
}
};
// Orca: track the current layer during the post-processing pass so that preheat M104s emitted
// for tool changes on the first layer use the correct first-layer temperature. The member
// m_layer_id is populated during the analysis pass and ends at the total layer count, so it
// cannot be used here — it would always select the "other layers" temperature for multi-layer
// prints.
unsigned int current_layer_id = 0;
// add lines M104 to exported gcode
auto process_line_T = [this, &export_line](const std::string& gcode_line, const size_t g1_lines_counter, const ExportLines::Backtrace& backtrace) {
auto process_line_T = [this, &export_line, &current_layer_id](const std::string& gcode_line, const size_t g1_lines_counter, const ExportLines::Backtrace& backtrace) {
const std::string cmd = GCodeReader::GCodeLine::extract_cmd(gcode_line);
int tool_number = -1;
@@ -1257,8 +1264,13 @@ void GCodeProcessor::run_post_process()
export_line.insert_lines(
backtrace, cmd,
// line inserter
[tool_number, this](unsigned int id, const std::vector<float>& time_diffs) {
const int temperature = int(m_layer_id != 1 ? m_filament_nozzle_temp[tool_number] :
[tool_number, this, &current_layer_id](unsigned int id, const std::vector<float>& time_diffs) {
// Orca: use the locally-tracked layer index (current_layer_id) rather than
// the stale m_layer_id from the analysis pass. current_layer_id == 0 means
// we haven't reached the first ;LAYER_CHANGE marker yet (e.g. tool change
// inside start gcode); == 1 means we are inside the first printed layer.
// Both cases should use the first-layer nozzle temperature.
const int temperature = int(current_layer_id > 1 ? m_filament_nozzle_temp[tool_number] :
m_filament_nozzle_temp_first_layer[tool_number]);
// Orca: M104.1 for XL printers, I can't find the documentation for this so I copied the C++ comments from
// Prusa-Firmware-Buddy here
@@ -1351,6 +1363,19 @@ void GCodeProcessor::run_post_process()
if (eol) {
++line_id;
const unsigned int internal_g1_lines_counter = export_line.update(gcode_line, line_id, g1_lines_counter);
// Orca: track the current layer for preheat temperature selection.
// The line is ";" + reserved_tag(Layer_Change) + EOL; match it independent of
// BBL vs. compatible flavor (which differ in the tag text).
if (gcode_line.size() > 1 && gcode_line.front() == ';') {
std::string_view tag_line(gcode_line);
// strip trailing CR/LF
while (!tag_line.empty() && (tag_line.back() == '\n' || tag_line.back() == '\r'))
tag_line.remove_suffix(1);
// strip leading ';'
tag_line.remove_prefix(1);
if (tag_line == reserved_tag(ETags::Layer_Change))
++current_layer_id;
}
// replace placeholder lines
bool processed = process_placeholders(gcode_line);
if (processed)
@@ -3796,9 +3821,10 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
return;
EMoveType type = move_type(delta_pos);
const float delta_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]));
m_travel_dist = delta_xyz;
if (type == EMoveType::Extrude) {
const float delta_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]));
m_travel_dist = delta_xyz;
float volume_extruded_filament = area_filament_cross_section * delta_pos[E];
float area_toolpath_cross_section = volume_extruded_filament / delta_xyz;
@@ -5420,6 +5446,11 @@ void GCodeProcessor::process_filament_change(int id)
int next_extruder_id = m_filament_maps[id];
int next_filament_id = id;
float extra_time = 0;
unsigned int filament_changes_delta = 0;
unsigned int extruder_changes_delta = 0;
float filament_load_time_delta = 0.0f;
float filament_unload_time_delta = 0.0f;
float tool_change_time_delta = 0.0f;
if (prev_filament_id == next_filament_id)
return;
@@ -5432,12 +5463,14 @@ void GCodeProcessor::process_filament_change(int id)
assert(prev_extruder_id != -1);
process_filaments(CustomGCode::ToolChange);
m_filament_id[next_extruder_id] = next_filament_id;
m_result.lock();
m_result.print_statistics.total_filament_changes += 1;
m_result.unlock();
extra_time += get_filament_unload_time(static_cast<size_t>(prev_filament_id));
filament_changes_delta += 1;
const float filament_unload_time = get_filament_unload_time(static_cast<size_t>(prev_filament_id));
extra_time += filament_unload_time;
filament_unload_time_delta += filament_unload_time;
m_time_processor.extruder_unloaded = false;
extra_time += get_filament_load_time(static_cast<size_t>(next_filament_id));
const float filament_load_time = get_filament_load_time(static_cast<size_t>(next_filament_id));
extra_time += filament_load_time;
filament_load_time_delta += filament_load_time;
}
else {
if (prev_extruder_id == -1) {
@@ -5445,7 +5478,9 @@ void GCodeProcessor::process_filament_change(int id)
m_extruder_id = next_extruder_id;
m_filament_id[next_extruder_id] = next_filament_id;
m_time_processor.extruder_unloaded = false;
extra_time += get_filament_load_time(static_cast<size_t>(next_filament_id));
const float filament_load_time = get_filament_load_time(static_cast<size_t>(next_filament_id));
extra_time += filament_load_time;
filament_load_time_delta += filament_load_time;
}
else {
//first process cache generated by last extruder
@@ -5456,24 +5491,39 @@ void GCodeProcessor::process_filament_change(int id)
//no filament in current extruder
m_filament_id[next_extruder_id] = next_filament_id;
m_time_processor.extruder_unloaded = false;
extra_time += get_filament_load_time(static_cast<size_t>(next_filament_id));
const float filament_load_time = get_filament_load_time(static_cast<size_t>(next_filament_id));
extra_time += filament_load_time;
filament_load_time_delta += filament_load_time;
}
else if (m_last_filament_id[next_extruder_id] != next_filament_id) {
//need to change filament
m_filament_id[next_extruder_id] = next_filament_id;
m_result.lock();
m_result.print_statistics.total_filament_changes += 1;
m_result.unlock();
extra_time += get_filament_unload_time(static_cast<size_t>(prev_filament_id));
filament_changes_delta += 1;
const float filament_unload_time = get_filament_unload_time(static_cast<size_t>(prev_filament_id));
extra_time += filament_unload_time;
filament_unload_time_delta += filament_unload_time;
m_time_processor.extruder_unloaded = false;
extra_time += get_filament_load_time(static_cast<size_t>(next_filament_id));
const float filament_load_time = get_filament_load_time(static_cast<size_t>(next_filament_id));
extra_time += filament_load_time;
filament_load_time_delta += filament_load_time;
}
m_result.lock();
m_result.print_statistics.total_extruder_changes++;
m_result.unlock();
extra_time += get_extruder_change_time(next_extruder_id);
extruder_changes_delta += 1;
const float tool_change_time = get_extruder_change_time(next_extruder_id);
extra_time += tool_change_time;
tool_change_time_delta += tool_change_time;
}
}
if (filament_changes_delta > 0 || extruder_changes_delta > 0 || filament_load_time_delta > 0.0f || filament_unload_time_delta > 0.0f || tool_change_time_delta > 0.0f) {
m_result.lock();
m_result.print_statistics.total_filament_changes += filament_changes_delta;
m_result.print_statistics.total_extruder_changes += extruder_changes_delta;
m_result.print_statistics.total_filament_load_time += filament_load_time_delta;
m_result.print_statistics.total_filament_unload_time += filament_unload_time_delta;
m_result.print_statistics.total_tool_change_time += tool_change_time_delta;
m_result.unlock();
}
m_cp_color.current = m_extruder_colors[next_filament_id];
simulate_st_synchronize(extra_time);
// store tool change move
@@ -5518,6 +5568,11 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type,
m_line_id + 1 :
((type == EMoveType::Seam) ? m_last_line_id : m_line_id);
if (type == EMoveType::Travel) {
m_result.print_statistics.total_travel_moves++;
m_result.print_statistics.total_travel_distance += m_travel_dist;
}
m_result.moves.push_back({
m_last_line_id,
type,
+14
View File
@@ -78,6 +78,13 @@ class Print;
std::array<Mode, static_cast<size_t>(ETimeMode::Count)> modes;
unsigned int total_filament_changes;
unsigned int total_extruder_changes;
float total_filament_load_time;
float total_filament_unload_time;
float total_tool_change_time;
float total_travel_distance;
unsigned int total_travel_moves;
float total_seam_gap_distance;
float total_seam_scarf_distance;
PrintEstimatedStatistics() { reset(); }
@@ -95,6 +102,13 @@ class Print;
used_filaments_per_role.clear();
total_filament_changes = 0;
total_extruder_changes = 0;
total_filament_load_time = 0.0f;
total_filament_unload_time = 0.0f;
total_tool_change_time = 0.0f;
total_travel_distance = 0.0f;
total_travel_moves = 0;
total_seam_gap_distance = 0.0f;
total_seam_scarf_distance = 0.0f;
}
};
+11 -5
View File
@@ -881,10 +881,10 @@ std::string GCodeWriter::_spiral_travel_to_z(double z, const Vec2d &ij_offset, c
// Determine number of segments based on Resolution
// --------------------------------------------------------------------
const double ref_resolution = 0.01; // reference resolution in mm
const double ref_segments = 16.0; // reference number of segments at reference resolution
const double ref_segments = 8.0; // reference number of segments at reference resolution
// number of linear segments to use for approximating the arc, clamp between 4 and 24
const int segments = std::clamp(int(std::round(ref_segments * (ref_resolution / m_resolution))), 4, 24);
// number of linear segments to use for approximating the arc, clamp between 4 and 16
const int segments = std::clamp(int(std::round(ref_segments * (ref_resolution / m_resolution))), 4, 16);
// --------------------------------------------------------------------
const double px = m_pos(0) - m_x_offset; // take plate offset into consideration
@@ -1109,9 +1109,13 @@ std::string GCodeWriter::unlift()
return gcode;
}
std::string GCodeWriter::set_fan(const GCodeFlavor gcode_flavor, unsigned int speed)
std::string GCodeWriter::set_fan(const GCodeFlavor gcode_flavor, unsigned int speed, unsigned int part_cooling_fan_min_pwm)
{
std::ostringstream gcode;
// ORCA: clamp non-zero fan commands up to the configured PWM floor so fans that can't spool at low duty
// cycles still start reliably. Zero (fan off) is preserved exactly so disable-fan commands are never altered.
if (speed > 0 && part_cooling_fan_min_pwm > 0 && speed < part_cooling_fan_min_pwm)
speed = part_cooling_fan_min_pwm;
if (speed == 0) {
switch (gcode_flavor) {
case gcfTeacup:
@@ -1146,7 +1150,9 @@ std::string GCodeWriter::set_fan(const GCodeFlavor gcode_flavor, unsigned int sp
std::string GCodeWriter::set_fan(unsigned int speed) const
{
//BBS
return GCodeWriter::set_fan(this->config.gcode_flavor, speed);
// ORCA: pick up the per-printer PWM floor from the active config.
return GCodeWriter::set_fan(this->config.gcode_flavor, speed,
static_cast<unsigned int>(std::max(0, this->config.part_cooling_fan_min_pwm.value)));
}
//BBS: set additional fan speed for BBS machine only
+3 -1
View File
@@ -98,7 +98,9 @@ public:
void set_xy_offset(double x, double y) { m_x_offset = x; m_y_offset = y; }
Vec2f get_xy_offset() { return Vec2f{m_x_offset, m_y_offset}; };
// To be called by the CoolingBuffer from another thread.
static std::string set_fan(const GCodeFlavor gcode_flavor, unsigned int speed);
// ORCA: `part_cooling_fan_min_pwm` (0-100, default 0) is a floor applied only when `speed` is non-zero, used to overcome
// PWM start-up thresholds on fans that won't spool below a certain duty cycle. A `speed` of 0 is always honoured.
static std::string set_fan(const GCodeFlavor gcode_flavor, unsigned int speed, unsigned int part_cooling_fan_min_pwm = 0);
// To be called by the main thread. It always emits the G-code, it does not remember the previous state.
// Keeping the state is left to the CoolingBuffer, which runs asynchronously on another thread.
std::string set_fan(unsigned int speed) const;
+5 -1
View File
@@ -298,7 +298,11 @@ bool directions_perpendicular(double angle1, double angle2, double max_diff = 0)
template<class T> bool contains(const std::vector<T> &vector, const Point &point);
template<typename T> T rad2deg(T angle) { return T(180.0) * angle / T(PI); }
double rad2deg_dir(double angle);
template<typename T> constexpr T deg2rad(const T angle) { return T(PI) * angle / T(180.0); }
template<typename T> constexpr T deg2rad(const T angle)
{
static_assert(std::is_floating_point<T>::value, "Why do you want to calculate angle in integer?");
return T(PI) * angle / T(180.0);
}
template<typename T> T angle_to_0_2PI(T angle)
{
static const T TWO_PI = T(2) * T(PI);
+6
View File
@@ -159,6 +159,12 @@ bool Layer::is_perimeter_compatible(const PrintRegion& a, const PrintRegion& b)
&& config.detect_thin_wall == other_config.detect_thin_wall
&& config.infill_wall_overlap == other_config.infill_wall_overlap
&& config.top_bottom_infill_wall_overlap == other_config.top_bottom_infill_wall_overlap
// Orca: these flags directly change the effective wall count produced by the perimeter
// generator. If two regions disagree on any of them, merging their slices into one shared make_perimeters
// call would silently use the first region's flag for both.
&& config.only_one_wall_first_layer == other_config.only_one_wall_first_layer
&& config.only_one_wall_top == other_config.only_one_wall_top
&& config.min_width_top_surface == other_config.min_width_top_surface
&& config.seam_slope_type == other_config.seam_slope_type
&& config.seam_slope_conditional == other_config.seam_slope_conditional
&& config.scarf_angle_threshold == other_config.scarf_angle_threshold
+69 -11
View File
@@ -1980,6 +1980,49 @@ void ModelVolume::reset_extra_facets()
this->fuzzy_skin_facets.reset();
}
std::optional<TriangleSelector::SavedPainting> ModelVolume::save_painting() const
{
if (is_any_painted() && is_model_part() && !mesh().empty()) {
TriangleSelector::SavedPainting sp;
sp.mesh = mesh();
sp.supported = supported_facets.get_data();
sp.seam = seam_facets.get_data();
sp.mmu = mmu_segmentation_facets.get_data();
sp.fuzzy = fuzzy_skin_facets.get_data();
return sp;
}
return {};
}
void ModelVolume::restore_painting(const std::optional<TriangleSelector::SavedPainting>& saved, const bool keep_existing_paint)
{
if (!keep_existing_paint) {
reset_extra_facets();
}
if (!saved) {
return;
}
auto remap_one = [&](const TriangleSelector::TriangleSplittingData& src_data,
FacetsAnnotation& target_facets) {
if (src_data.bitstream.empty())
return;
auto result =
TriangleSelector::remap_painting(saved->mesh.its, src_data, mesh().its, Geometry::translation_transform(mesh().get_init_shift()),
keep_existing_paint ?
std::optional<std::reference_wrapper<const TriangleSelector::TriangleSplittingData>>{std::ref(target_facets.get_data())} :
std::optional<std::reference_wrapper<const TriangleSelector::TriangleSplittingData>>{});
if (!result.bitstream.empty())
target_facets.set_data(std::move(result));
};
remap_one(saved->supported, supported_facets);
remap_one(saved->seam, seam_facets);
remap_one(saved->mmu, mmu_segmentation_facets);
remap_one(saved->fuzzy, fuzzy_skin_facets);
}
static void invalidate_translations(ModelObject* object, const ModelInstance* src_instance)
{
if (!object->origin_translation.isApprox(Vec3d::Zero()) && src_instance->get_offset().isApprox(Vec3d::Zero())) {
@@ -1993,13 +2036,14 @@ static void invalidate_translations(ModelObject* object, const ModelInstance* sr
}
}
void ModelObject::split(ModelObjectPtrs* new_objects)
void ModelObject::split(ModelObjectPtrs* new_objects, const bool remap_paint)
{
std::vector<TriangleMesh> all_meshes;
std::vector<Transform3d> all_transfos;
std::vector<std::pair<int, int>> volume_mesh_counts;
all_meshes.reserve(this->volumes.size() * 5);
bool is_multi_volume_object = (this->volumes.size() > 1);
std::optional<TriangleSelector::SavedPainting> saved_painting;
for (int volume_idx = 0; volume_idx < this->volumes.size(); volume_idx++) {
ModelVolume* volume = this->volumes[volume_idx];
@@ -2011,6 +2055,10 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
volume->text_configuration.reset();
if (!is_multi_volume_object) {
if (remap_paint) {
// Save painting so we could restore them after the mesh split
saved_painting = volume->save_painting();
}
//BBS: not multi volume object, then split mesh.
std::vector<TriangleMesh> volume_meshes = volume->mesh().split();
int mesh_count = 0;
@@ -2078,10 +2126,19 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
ModelVolume* new_vol = new_object->add_volume(*volume, std::move(mesh));
if (is_multi_volume_object) {
// BBS: volume geometry not changed, so we can keep the color paint facets
if (new_vol->mmu_segmentation_facets.timestamp() == volume->mmu_segmentation_facets.timestamp())
new_vol->mmu_segmentation_facets.reset(); // BBS: let next assign take effect
new_vol->mmu_segmentation_facets.assign(volume->mmu_segmentation_facets);
// BBS: volume geometry not changed, so we can keep the paint facets
#define COPY_FACETS(f) \
if (new_vol->f.timestamp() == volume->f.timestamp()) \
new_vol->f.reset(); /* BBS: let next assign take effect */ \
new_vol->f.assign(volume->f)
COPY_FACETS(supported_facets);
COPY_FACETS(seam_facets);
COPY_FACETS(mmu_segmentation_facets);
COPY_FACETS(fuzzy_skin_facets);
} else if (saved_painting) {
// Geometry changed, attempt to remap them to the new mesh
new_vol->restore_painting(saved_painting);
}
// BBS: clear volume's config, as we already set them into object
@@ -2682,7 +2739,7 @@ std::string ModelVolume::type_to_string(const ModelVolumeType t)
// Split this volume, append the result to the object owning this volume.
// Return the number of volumes created from this one.
// This is useful to assign different materials to different volumes of an object.
size_t ModelVolume::split(unsigned int max_extruders)
size_t ModelVolume::split(unsigned int max_extruders, bool remap_paint)
{
std::vector<TriangleMesh> meshes = this->mesh().split();
if (meshes.size() <= 1)
@@ -2692,6 +2749,9 @@ size_t ModelVolume::split(unsigned int max_extruders)
if (text_configuration.has_value())
text_configuration.reset();
std::optional<TriangleSelector::SavedPainting> saved_painting = remap_paint ? save_painting() :
std::optional<TriangleSelector::SavedPainting>{};
size_t idx = 0;
size_t ivolume = std::find(this->object->volumes.begin(), this->object->volumes.end(), this) - this->object->volumes.begin();
const std::string name = this->name;
@@ -2714,11 +2774,7 @@ size_t ModelVolume::split(unsigned int max_extruders)
this->source = ModelVolume::Source();
// BBS: reset facet annotations
this->mmu_segmentation_facets.reset();
this->exterior_facets.reset();
this->supported_facets.reset();
this->seam_facets.reset();
this->fuzzy_skin_facets.reset();
this->reset_extra_facets();
}
else
this->object->volumes.insert(this->object->volumes.begin() + (++ivolume), new ModelVolume(object, *this, std::move(mesh)));
@@ -2731,6 +2787,8 @@ size_t ModelVolume::split(unsigned int max_extruders)
this->object->volumes[ivolume]->config.set("extruder", this->extruder_id());
//this->object->volumes[ivolume]->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter));
this->object->volumes[ivolume]->m_is_splittable = 0;
this->object->volumes[ivolume]->restore_painting(saved_painting);
++ idx;
}
+12 -5
View File
@@ -516,7 +516,7 @@ public:
void delete_connectors();
void clone_for_cut(ModelObject **obj);
void split(ModelObjectPtrs*new_objects);
void split(ModelObjectPtrs*new_objects, bool remap_paint);
void merge();
// BBS: Boolean opts - Musang King
@@ -731,6 +731,7 @@ public:
void assign(const FacetsAnnotation &rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } }
void assign(FacetsAnnotation &&rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } }
const TriangleSelector::TriangleSplittingData &get_data() const noexcept { return m_data; }
void set_data(TriangleSelector::TriangleSplittingData &&data) { m_data = std::move(data); this->touch(); }
bool set(const TriangleSelector& selector);
indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const;
// BBS
@@ -877,13 +878,18 @@ public:
// List of mesh facets painted for fuzzy skin.
FacetsAnnotation fuzzy_skin_facets;
// Save painting data before reset_extra_facets() discards it.
// Used for replacing mesh without losing painting data.
// Only for model parts (not modifiers/connectors).
std::optional<TriangleSelector::SavedPainting> save_painting() const;
// Remap painting data from previous saved source to this mesh
void restore_painting(const std::optional<TriangleSelector::SavedPainting>& saved, bool keep_existing_paint = false);
// BBS: quick access for volume extruders, 1 based
mutable std::vector<int> mmuseg_extruders;
mutable Timestamp mmuseg_ts;
// List of exterior faces
FacetsAnnotation exterior_facets;
// Is set only when volume is Embossed Text type
// Contain information how to re-create volume
std::optional<TextConfiguration> text_configuration;
@@ -924,7 +930,7 @@ public:
// Split this volume, append the result to the object owning this volume.
// Return the number of volumes created from this one.
// This is useful to assign different materials to different volumes of an object.
size_t split(unsigned int max_extruders);
size_t split(unsigned int max_extruders, bool remap_paint);
void translate(double x, double y, double z) { translate(Vec3d(x, y, z)); }
void translate(const Vec3d& displacement);
void scale(const Vec3d& scaling_factors);
@@ -1007,6 +1013,7 @@ public:
bool is_seam_painted() const { return !this->seam_facets.empty(); }
bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); }
bool is_fuzzy_skin_painted() const { return !this->fuzzy_skin_facets.empty(); }
bool is_any_painted() const { return is_fdm_support_painted() || is_seam_painted() || is_mm_painted() || is_fuzzy_skin_painted(); }
// Orca: Implement prusa's filament shrink compensation approach
// Returns 0-based indices of extruders painted by multi-material painting gizmo.
+40 -31
View File
@@ -581,14 +581,6 @@ void Preset::load_info(const std::string& file)
catch (...) {
return;
}
//TODO: workaround for current info file convert, will remove it later
if (this->updated_time == 0) {
this->updated_time = (long long)Slic3r::Utils::get_current_time_utc();
//this->sync_info = "update";
BOOST_LOG_TRIVIAL(info) << boost::format("old info file, updated time to %1%") % this->updated_time;
save_info();
}
}
void Preset::save_info(std::string file)
@@ -1014,6 +1006,7 @@ static std::vector<std::string> s_Preset_print_options{
"is_infill_first",
"sparse_infill_density",
"fill_multiline",
"gyroid_optimized",
"sparse_infill_pattern",
"lateral_lattice_angle_1",
"lateral_lattice_angle_2",
@@ -1335,7 +1328,7 @@ static std::vector<std::string> s_Preset_machine_limits_options {
static std::vector<std::string> s_Preset_printer_options {
"printer_technology",
"printable_area", "extruder_printable_area", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor",
"fan_kickstart", "fan_speedup_time", "fan_speedup_overhangs",
"fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_time", "fan_speedup_overhangs",
"single_extruder_multi_material", "manual_filament_change", "file_start_gcode", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "printing_by_object_gcode", "layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode", "change_filament_gcode", "change_extrusion_role_gcode",
"printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type",
"printable_height", "extruder_printable_height", "extruder_clearance_radius", "extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod",
@@ -1352,7 +1345,7 @@ static std::vector<std::string> s_Preset_printer_options {
"use_relative_e_distances", "extruder_type", "use_firmware_retraction", "printer_notes",
"grab_length", "support_object_skip_flush", "physical_extruder_map",
"cooling_tube_retraction",
"cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "wipe_tower_type", "purge_in_prime_tower", "enable_filament_ramming",
"cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "wipe_tower_type", "purge_in_prime_tower", "enable_filament_ramming", "tool_change_on_wipe_tower",
"z_offset",
"disable_m73", "preferred_orientation", "emit_machine_limits_to_gcode", "pellet_modded_printer", "support_multi_bed_types", "default_bed_type", "bed_mesh_min","bed_mesh_max","bed_mesh_probe_distance", "adaptive_bed_mesh_margin", "enable_long_retraction_when_cut","long_retractions_when_cut","retraction_distances_when_cut",
"bed_temperature_formula", "nozzle_flush_dataset",
@@ -2205,19 +2198,29 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
}
}
// base_id
if (preset_values.find(BBL_JSON_KEY_BASE_ID) == preset_values.end()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find base_id, not loading for user preset %1%") % canonical_name;
unlock();
return false;
// base_id is only required for presets inheriting from a parent. Root presets
// with an empty "inherits" field intentionally have no base_id.
std::string based_id;
const auto base_id = preset_values.find(BBL_JSON_KEY_BASE_ID);
if (base_id != preset_values.end()) {
based_id = base_id->second;
} else {
const auto inherits_iter = preset_values.find(BBL_JSON_KEY_INHERITS);
const bool preset_inherits_from_parent = inherits_iter != preset_values.end() && !inherits_iter->second.empty();
if (preset_inherits_from_parent) {
// This indicates that there is inherits exists but there is no base_id
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__
<< boost::format("can not find base_id, not loading for user preset %1%") % canonical_name;
unlock();
return false;
}
}
std::string cloud_base_id = preset_values[BBL_JSON_KEY_BASE_ID];
//filament_id
std::string cloud_filament_id;
if ((m_type == Preset::TYPE_FILAMENT) && preset_values.find(BBL_JSON_KEY_FILAMENT_ID) != preset_values.end()) {
cloud_filament_id = preset_values[BBL_JSON_KEY_FILAMENT_ID];
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << canonical_name << " filament_id: " << cloud_filament_id << " base_id: " << cloud_base_id;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << canonical_name << " filament_id: " << cloud_filament_id << " base_id: " << based_id;
}
DynamicPrintConfig new_config, cloud_config;
@@ -2279,6 +2282,10 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
if (iter->name == m_edited_preset.name && iter->is_dirty) {
// Keep modifies when update from remote
new_config.apply_only(m_edited_preset.config, m_edited_preset.config.diff(iter->config));
} else if (iter->name == m_edited_preset.name) {
// Preset is not dirty (no local unsaved changes) — also update the edited preset
// to prevent a false "dirty" indication (orange highlight) after a silent cloud sync
m_edited_preset.config = new_config;
}
iter->config = new_config;
iter->updated_time = cloud_update_time;
@@ -2286,7 +2293,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
iter->version = cloud_version.value();
iter->user_id = cloud_user_id;
iter->setting_id = cloud_setting_id;
iter->base_id = cloud_base_id;
iter->base_id = based_id;
iter->filament_id = cloud_filament_id;
update_alias(*iter);
//presets_loaded.emplace_back(*it->second);
@@ -2305,7 +2312,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
preset.version = cloud_version.value();
preset.user_id = cloud_user_id;
preset.setting_id = cloud_setting_id;
preset.base_id = cloud_base_id;
preset.base_id = based_id;
preset.filament_id = cloud_filament_id;
update_alias(preset);
@@ -3666,20 +3673,22 @@ void PresetCollection::set_custom_preset_alias(Preset &preset)
// For printers, there is nothing to remove
// For prints AKA processes, the postfix should be kept
// Alias should be set here, as the preset name may be augmented further later (i.e., prefixing relative path for bundles)
std::string alias_name;
std::string preset_name = get_preset_bare_name(preset.name);
if (m_type == Preset::Type::TYPE_FILAMENT && preset.config.has(BBL_JSON_KEY_INHERITS) && preset.config.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS)->value.empty()) {
if (alias_name.empty()) {
size_t end_pos = preset_name.find_first_of("@");
if (end_pos != std::string::npos) {
alias_name = preset_name.substr(0, end_pos);
boost::trim_right(alias_name);
}
std::string bare_preset_name = get_preset_bare_name(preset.name);
std::string alias_name = bare_preset_name;
const bool is_root_filament_preset =
m_type == Preset::Type::TYPE_FILAMENT &&
preset.config.has(BBL_JSON_KEY_INHERITS) &&
preset.config.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS)->value.empty();
if (is_root_filament_preset) {
const size_t suffix_separator_pos = bare_preset_name.find_first_of("@");
if (suffix_separator_pos != std::string::npos) {
alias_name = bare_preset_name.substr(0, suffix_separator_pos);
boost::trim_right(alias_name);
if (alias_name.empty())
alias_name = bare_preset_name;
}
}
else {
alias_name = preset_name;
}
preset.alias = std::move(alias_name);
m_map_alias_to_profile_name[preset.alias].push_back(preset.name);
+42 -4
View File
@@ -614,13 +614,24 @@ VendorType PresetBundle::get_current_vendor_type()
{
auto t = VendorType::Unknown;
auto config = &printers.get_edited_preset().config;
const auto* printer_model = config->opt<ConfigOptionString>("printer_model");
if (printer_model == nullptr) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": printer_model is "
<< (config->has("printer_model") ? "not a string" : "missing")
<< ", vendor type is Unknown";
return t;
}
std::string vendor_name;
for (auto vendor_profile : vendors) {
for (auto vendor_model : vendor_profile.second.models)
if (vendor_model.name == config->opt_string("printer_model")) {
for (const auto& vendor_profile : vendors) {
for (const auto& vendor_model : vendor_profile.second.models) {
if (vendor_model.name == printer_model->value) {
vendor_name = vendor_profile.first;
break;
}
}
if (!vendor_name.empty())
break;
}
if (!vendor_name.empty())
{
@@ -2622,6 +2633,9 @@ void PresetBundle::update_selections(AppConfig &config)
break;
this->filament_presets.emplace_back(remove_ini_suffix(f_name));
}
update_filament_count();
std::vector<std::string> filament_colors;
auto f_colors = config.get_printer_setting(initial_printer_profile_name, "filament_colors");
if (!f_colors.empty()) {
@@ -2763,6 +2777,8 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
this->filament_presets.emplace_back(remove_ini_suffix(f_name));
}
update_filament_count();
// Load data from AppConfig to ProjectConfig when Studio is initialized.
std::vector<std::string> filament_colors;
auto f_colors = config.get_printer_setting(initial_printer_profile_name, "filament_colors");
@@ -3782,11 +3798,33 @@ int PresetBundle::get_printer_extruder_count() const
{
const Preset& printer_preset = this->printers.get_edited_preset();
int count = printer_preset.config.option<ConfigOptionFloats>("nozzle_diameter")->values.size();
const auto* nozzle_diameter = printer_preset.config.option<ConfigOptionFloats>("nozzle_diameter");
if (nozzle_diameter == nullptr) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": nozzle_diameter is missing, using 1 extruder";
return 1;
}
if (nozzle_diameter->values.empty()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": nozzle_diameter is empty, using 1 extruder";
return 1;
}
int count = int(nozzle_diameter->values.size());
return count;
}
void PresetBundle::update_filament_count()
{
if (printers.get_edited_preset().printer_technology() != ptFFF)
return;
const size_t num_extruders = static_cast<size_t>(get_printer_extruder_count());
if (filament_presets.size() >= num_extruders)
return;
filament_presets.resize(num_extruders, filament_presets.empty()
? filaments.first_visible().name
: filament_presets.back());
}
bool PresetBundle::support_different_extruders()
{
Preset& printer_preset = this->printers.get_edited_preset();
+6
View File
@@ -372,6 +372,12 @@ public:
int get_printer_extruder_count() const;
bool support_different_extruders();
// Orca: Ensure filament_presets has at least one slot per nozzle on FFF printers.
// Called from (load|update)_selections before the parallel project_config arrays
// (filament_colour/colour_type/map) are sized off filament_presets.size(), so a
// short saved filament list doesn't truncate the loaded colors.
void update_filament_count();
// Load user configuration and store it into the user profiles.
// This method is called by the configuration wizard.
void load_config_from_wizard(const std::string &name, DynamicPrintConfig config, Semver file_version)
+34 -31
View File
@@ -134,6 +134,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"fan_cooling_layer_time",
"full_fan_speed_layer",
"fan_kickstart",
"part_cooling_fan_min_pwm",
"fan_speedup_overhangs",
"fan_speedup_time",
"filament_colour",
@@ -360,6 +361,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "wipe_tower_filament"
|| opt_key == "wiping_volumes_extruders"
|| opt_key == "enable_filament_ramming"
|| opt_key == "tool_change_on_wipe_tower"
|| opt_key == "purge_in_prime_tower"
|| opt_key == "z_offset"
|| opt_key == "support_multi_bed_types"
@@ -1785,46 +1787,47 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
};
std::string warning_key;
const auto max_junction_deviation = m_config.machine_max_junction_deviation.values[0];
const bool ignore_jerk_validation = m_config.gcode_flavor == gcfMarlinFirmware && max_junction_deviation > 0;
// check jerk
if (m_default_object_config.default_jerk == 1 || m_default_object_config.outer_wall_jerk == 1 ||
m_default_object_config.inner_wall_jerk == 1) {
warning->string = L("Setting the jerk speed too low could lead to artifacts on curved surfaces");
if (m_default_object_config.outer_wall_jerk == 1)
warning_key = "outer_wall_jerk";
else if (m_default_object_config.inner_wall_jerk == 1)
warning_key = "inner_wall_jerk";
else
warning_key = "default_jerk";
if (!ignore_jerk_validation) {
if (m_default_object_config.default_jerk == 1 || m_default_object_config.outer_wall_jerk == 1 ||
m_default_object_config.inner_wall_jerk == 1) {
warning->string = L("Setting the jerk speed too low could lead to artifacts on curved surfaces");
if (m_default_object_config.outer_wall_jerk == 1)
warning_key = "outer_wall_jerk";
else if (m_default_object_config.inner_wall_jerk == 1)
warning_key = "inner_wall_jerk";
else
warning_key = "default_jerk";
warning->opt_key = warning_key;
}
warning->opt_key = warning_key;
}
if (warning_key.empty() && m_default_object_config.default_jerk > 0) {
std::vector<std::string> jerk_to_check = {"default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk",
"top_surface_jerk", "initial_layer_jerk", "travel_jerk"};
const auto max_jerk = std::min(m_config.machine_max_jerk_x.values[0], m_config.machine_max_jerk_y.values[0]);
warning_key.clear();
if (m_default_object_config.default_jerk > 0)
warning_key = check_motion_ability_object_setting(jerk_to_check, max_jerk);
if (!warning_key.empty()) {
warning->string = L(
"The jerk setting exceeds the printer's maximum jerk (machine_max_jerk_x/machine_max_jerk_y).\nOrca will "
"automatically cap the jerk speed to ensure it doesn't surpass the printer's capabilities.\nYou can adjust the "
"maximum jerk setting in your printer's configuration to get higher speeds.");
warning->opt_key = warning_key;
}
if (warning_key.empty() && m_default_object_config.default_jerk > 0) {
std::vector<std::string> jerk_to_check = {"default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk",
"top_surface_jerk", "initial_layer_jerk", "travel_jerk"};
const auto max_jerk = std::min(m_config.machine_max_jerk_x.values[0], m_config.machine_max_jerk_y.values[0]);
warning_key.clear();
warning_key = check_motion_ability_object_setting(jerk_to_check, max_jerk);
if (!warning_key.empty()) {
warning->string = L(
"The jerk setting exceeds the printer's maximum jerk (machine_max_jerk_x/machine_max_jerk_y).\n"
"Orca will automatically cap the jerk speed to ensure it doesn't surpass the printer's capabilities.\n"
"You can adjust the maximum jerk setting in your printer's configuration to get higher speeds.");
warning->opt_key = warning_key;
}
}
}
// Check junction deviation
const auto max_junction_deviation = m_config.machine_max_junction_deviation.values[0];
// Orca: Only marlin FW supports max junction deviation. Dont display warning if firmware is not supporting it.
const bool support_max_junction_deviation = ( m_config.gcode_flavor == gcfMarlinFirmware);
if (warning_key.empty() && m_default_object_config.default_junction_deviation.value > max_junction_deviation && support_max_junction_deviation) {
warning->string = L( "Junction deviation setting exceeds the printer's maximum value "
"(machine_max_junction_deviation).\nOrca will "
"automatically cap the junction deviation to ensure it doesn't surpass the printer's "
"capabilities.\nYou can adjust the "
"machine_max_junction_deviation value in your printer's configuration to get higher limits.");
warning->string = L( "Junction deviation setting exceeds the printer's maximum value (machine_max_junction_deviation).\n"
"Orca will automatically cap the junction deviation to ensure it doesn't surpass the printer's capabilities.\n"
"You can adjust the machine_max_junction_deviation value in your printer's configuration to get higher limits.");
warning->opt_key = warning_key;
}
+103 -2
View File
@@ -2931,6 +2931,19 @@ void PrintConfigDef::init_fff_params()
def->max = 10; // Maximum number of lines for infill pattern
def->set_default_value(new ConfigOptionInt(1));
// Z-buckling bias optimization (experimental). Tightens the gyroid wave along the Z
// (vertical) axis at low infill density to shorten the effective column length under
// Z-axis compression. Filament use at the same `sparse_infill_density` setting is
// preserved. No effect above ~30% density (formula clamps to no-op).
def = this->add("gyroid_optimized", coBool);
def->label = L("Z-buckling bias optimization (experimental)");
def->category = L("Strength");
def->tooltip = L("Tightens the gyroid wave along the Z (vertical) axis at low infill density "
"to shorten the effective vertical column length and improve Z-axis compression "
"buckling resistance. Filament use is preserved. No effect at ~30% sparse infill "
"density and above. Only applies when Sparse infill pattern is set to Gyroid.");
def->set_default_value(new ConfigOptionBool(false));
def = this->add("sparse_infill_pattern", coEnum);
def->label = L("Sparse infill pattern");
def->category = L("Strength");
@@ -3743,6 +3756,28 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0));
// ORCA: minimum non-zero part cooling fan speed.
def = this->add("part_cooling_fan_min_pwm", coInt);
def->label = L("Minimum non-zero part cooling fan speed");
def->tooltip = L("Some part-cooling fans cannot start spinning when commanded below a certain PWM duty cycle. "
"When set above 0, any non-zero part-cooling fan command will be raised to at least this percentage "
"so the fan reliably starts. A fan command of 0 (fan off) is always honoured exactly. "
"This clamp is applied after every other fan calculation (first-layer ramp, layer-time interpolation, "
"overhang/bridge/support-interface/ironing overrides), so scaling still operates within the range "
"[this value, 100%]."
"\nIf your firmware already disables the fan below a threshold (for example Klipper's "
"[fan] off_below: 0.10 shuts the fan off whenever the commanded duty cycle is below 10%), "
"this option and the firmware threshold should ideally be set to the same value. Matching them "
"(e.g. off_below: 0.10 in Klipper and 10% here) guarantees the slicer never emits a non-zero "
"value that the firmware would silently drop, and the fan never receives a value below the one "
"you know it can actually spool at."
"\nSet to 0 to deactivate.");
def->sidetext = L("%");
def->min = 0;
def->max = 100;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInt(0));
def = this->add("time_cost", coFloat);
def->label = L("Time cost");
@@ -4256,7 +4291,7 @@ void PrintConfigDef::init_fff_params()
def->min = 0;
def->max = 90;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0));
def->set_default_value(new ConfigOptionFloat(35));
def = this->add("zaa_dont_alternate_fill_direction", coBool);
def->label = L("Don't alternate fill direction");
@@ -5953,6 +5988,16 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(true));
def = this->add("tool_change_on_wipe_tower", coBool);
def->label = L("Tool change on wipe tower");
def->tooltip = L("Force the toolhead to travel to the wipe tower before issuing the tool change command (Tx). "
"Only relevant for multi-extruder (multi-toolhead) printers using a Type 2 wipe tower. "
"By default Orca skips the travel on multi-toolhead machines because the firmware handles the head swap, "
"which can result in the Tx command being issued above the printed part. "
"Enable this option if you want the tool change to always be issued above the wipe tower instead.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("wipe_tower_no_sparse_layers", coBool);
def->label = L("No sparse layers (beta)");
@@ -6872,7 +6917,7 @@ void PrintConfigDef::init_fff_params()
def->enum_labels.emplace_back(L("Cone"));
def->enum_labels.emplace_back(L("Rib"));
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<WipeTowerWallType>(wtwRectangle));
def->set_default_value(new ConfigOptionEnum<WipeTowerWallType>(wtwRib));
def = this->add("wipe_tower_extra_rib_length", coFloat);
def->label = L("Extra rib length");
@@ -9736,11 +9781,19 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen
case coStrings:
{
ConfigOptionStrings * opt = this->option<ConfigOptionStrings>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<std::string> new_values;
new_values.resize(filament_count);
for (int f_index = 0; f_index < filament_count; f_index++)
{
if (variant_index[f_index] < 0 || static_cast<size_t>(variant_index[f_index]) >= opt->size()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index];
continue;
}
new_values[f_index] = opt->get_at(variant_index[f_index]);
}
opt->values = new_values;
@@ -9749,11 +9802,19 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen
case coInts:
{
ConfigOptionInts * opt = this->option<ConfigOptionInts>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<int> new_values;
new_values.resize(filament_count);
for (int f_index = 0; f_index < filament_count; f_index++)
{
if (variant_index[f_index] < 0 || static_cast<size_t>(variant_index[f_index]) >= opt->size()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index];
continue;
}
new_values[f_index] = opt->get_at(variant_index[f_index]);
}
opt->values = new_values;
@@ -9762,11 +9823,19 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen
case coFloats:
{
ConfigOptionFloats * opt = this->option<ConfigOptionFloats>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<double> new_values;
new_values.resize(filament_count);
for (int f_index = 0; f_index < filament_count; f_index++)
{
if (variant_index[f_index] < 0 || static_cast<size_t>(variant_index[f_index]) >= opt->size()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index];
continue;
}
new_values[f_index] = opt->get_at(variant_index[f_index]);
}
opt->values = new_values;
@@ -9775,11 +9844,19 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen
case coPercents:
{
ConfigOptionPercents * opt = this->option<ConfigOptionPercents>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<double> new_values;
new_values.resize(filament_count);
for (int f_index = 0; f_index < filament_count; f_index++)
{
if (variant_index[f_index] < 0 || static_cast<size_t>(variant_index[f_index]) >= opt->size()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index];
continue;
}
new_values[f_index] = opt->get_at(variant_index[f_index]);
}
opt->values = new_values;
@@ -9788,11 +9865,19 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen
case coFloatsOrPercents:
{
ConfigOptionFloatsOrPercents * opt = this->option<ConfigOptionFloatsOrPercents>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<FloatOrPercent> new_values;
new_values.resize(filament_count);
for (int f_index = 0; f_index < filament_count; f_index++)
{
if (variant_index[f_index] < 0 || static_cast<size_t>(variant_index[f_index]) >= opt->size()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index];
continue;
}
new_values[f_index] = opt->get_at(variant_index[f_index]);
}
opt->values = new_values;
@@ -9801,11 +9886,19 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen
case coBools:
{
ConfigOptionBools * opt = this->option<ConfigOptionBools>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<unsigned char> new_values;
new_values.resize(filament_count);
for (int f_index = 0; f_index < filament_count; f_index++)
{
if (variant_index[f_index] < 0 || static_cast<size_t>(variant_index[f_index]) >= opt->size()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index];
continue;
}
new_values[f_index] = opt->get_at(variant_index[f_index]);
}
opt->values = new_values;
@@ -9814,11 +9907,19 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen
case coEnums:
{
ConfigOptionEnumsGeneric * opt = this->option<ConfigOptionEnumsGeneric>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<int> new_values;
new_values.resize(filament_count);
for (int f_index = 0; f_index < filament_count; f_index++)
{
if (variant_index[f_index] < 0 || static_cast<size_t>(variant_index[f_index]) >= opt->size()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index];
continue;
}
new_values[f_index] = opt->get_at(variant_index[f_index]);
}
opt->values = new_values;
+6
View File
@@ -1160,6 +1160,7 @@ PRINT_CONFIG_CLASS_DEFINE(
// Orca:
((ConfigOptionFloatOrPercent, infill_combination_max_layer_height))
((ConfigOptionInt, fill_multiline))
((ConfigOptionBool, gyroid_optimized))
// Ironing options
((ConfigOptionEnum<IroningType>, ironing_type))
((ConfigOptionEnum<InfillPattern>, ironing_pattern))
@@ -1336,6 +1337,10 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloat, fan_kickstart))
((ConfigOptionBool, fan_speedup_overhangs))
((ConfigOptionFloat, fan_speedup_time))
// ORCA: minimum PWM (as a percent 0-100) emitted when the part-cooling fan is asked for a non-zero speed.
// Used to overcome the PWM start-up threshold on fans that cannot spool below a certain duty cycle.
// A value of 0 (the default) leaves behaviour unchanged. A fan command of 0 (off) is always honoured.
((ConfigOptionInt, part_cooling_fan_min_pwm))
((ConfigOptionFloats, filament_diameter))
((ConfigOptionBoolsNullable, filament_adaptive_volumetric_speed))
((ConfigOptionStrings, volumetric_speed_coefficients))
@@ -1489,6 +1494,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionEnum<WipeTowerType>, wipe_tower_type))
((ConfigOptionBool, purge_in_prime_tower))
((ConfigOptionBool, enable_filament_ramming))
((ConfigOptionBool, tool_change_on_wipe_tower))
((ConfigOptionBool, support_multi_bed_types))
// Small Area Infill Flow Compensation
+28 -18
View File
@@ -21,6 +21,32 @@ namespace Slic3r {
bool PrintObject::clip_multipart_objects = true;
bool PrintObject::infill_only_where_needed = false;
static coordf_t compute_slice_z(PrintObject* print_object, size_t i_layer, coordf_t lo, coordf_t hi)
{
bool zaa_active = false;
coordf_t z_offset = 0.0;
size_t num_regions = print_object->num_printing_regions();
for (size_t rid = 0; rid < num_regions; ++rid) {
const auto& rcfg = print_object->printing_region(rid).config();
if (rcfg.zaa_enabled) {
if (!zaa_active || rcfg.zaa_min_z < z_offset)
z_offset = rcfg.zaa_min_z;
zaa_active = true;
}
}
if (!zaa_active || i_layer == 0) {
return 0.5 * (lo + hi);
}
coordf_t slice_z = lo + z_offset;
if ((slice_z < lo && !is_approx(slice_z, lo)) || (slice_z > hi && !is_approx(slice_z, hi))) {
throw RuntimeError("Bad min Z value");
}
return slice_z;
}
LayerPtrs new_layers(
PrintObject *print_object,
// Object layers (pairs of bottom/top Z coordinate), without the raft.
@@ -34,24 +60,8 @@ LayerPtrs new_layers(
for (size_t i_layer = 0; i_layer < object_layers.size(); i_layer += 2) {
coordf_t lo = object_layers[i_layer];
coordf_t hi = object_layers[i_layer + 1];
coordf_t slice_z = 0.5 * (lo + hi);
bool zaa_active = false;
coordf_t z_offset = 0.0;
size_t num_regions = print_object->num_printing_regions();
for (size_t rid = 0; rid < num_regions; ++rid) {
const auto &rcfg = print_object->printing_region(rid).config();
if (rcfg.zaa_enabled) {
if (!zaa_active || rcfg.zaa_min_z < z_offset)
z_offset = rcfg.zaa_min_z;
zaa_active = true;
}
}
if (zaa_active) {
slice_z = lo + z_offset;
if ((slice_z < lo && !is_approx(slice_z, lo)) || (slice_z > hi && !is_approx(slice_z, hi))) {
throw RuntimeError("Bad min Z value");
}
}
coordf_t slice_z = compute_slice_z(print_object, i_layer, lo, hi);
Layer *layer = new Layer(id ++, print_object, hi - lo, hi + zmin, slice_z);
out.emplace_back(layer);
if (prev != nullptr) {
+17 -5
View File
@@ -46,11 +46,23 @@ void name_tbb_thread_pool_threads_set_locale();
template<class Fn>
inline boost::thread create_thread(boost::thread::attributes &attrs, Fn &&fn)
{
// Duplicating the stack allocation size of Thread Building Block worker
// threads of the thread pool: allocate 4MB on a 64bit system, allocate 2MB
// on a 32bit system by default.
attrs.set_stack_size((sizeof(void*) == 4) ? (2048 * 1024) : (4096 * 1024));
// Stack size for our worker threads. Originally duplicated TBB's pool
// default (4 MB), but the Emboss text-cut path calls into CGAL's
// Polygon_mesh_processing::corefine, which falls back from filtered
// interval arithmetic to exact rational arithmetic (mpq_class) on
// near-degenerate input, and the constrained 2D triangulation walker
// (Triangulation_2::march_locate_2D) can recurse deeply enough to
// exceed 4 MB on real models -- producing a SIGBUS at the next thread's
// stack guard page on macOS / Linux.
//
// 16 MB chosen as 4x defensive headroom over the observed crash
// threshold (n=1 reproducer at exactly 4 MB on macOS arm64). All three
// platforms defer-commit reserved stack pages: macOS / Linux mmap the
// stack and only fault in pages on touch; Boost.Thread on Win32 passes
// STACK_SIZE_PARAM_IS_A_RESERVATION to _beginthreadex, so the value is
// a reserve, not the initial commit. Resident memory therefore stays
// proportional to actual stack depth on every target.
attrs.set_stack_size(16 * 1024 * 1024);
return boost::thread{attrs, std::forward<Fn>(fn)};
}
+1 -1
View File
@@ -2348,7 +2348,7 @@ void cut_mesh(const indexed_triangle_set& mesh, float z, indexed_triangle_set* u
IntersectionLines upper_lines, lower_lines;
std::vector<int> upper_slice_vertices, lower_slice_vertices;
std::vector<Vec3i32> facets_edge_ids = its_face_edge_ids(mesh);
std::map<int, Vec3f> section_vertices_map;
std::map<int, Vec3f> section_vertices_map; // vertices on the cut plane
for (int facet_idx = 0; facet_idx < int(mesh.indices.size()); ++ facet_idx) {
const stl_triangle_vertex_indices &facet = mesh.indices[facet_idx];
+338 -22
View File
@@ -1,9 +1,11 @@
#include "TriangleSelector.hpp"
#include "Model.hpp"
#include "AABBTreeIndirect.hpp"
#include <boost/container/small_vector.hpp>
#include <boost/log/trivial.hpp>
#include <cstddef>
#include <functional>
#include <tbb/parallel_for.h>
#ifndef NDEBUG
@@ -168,24 +170,69 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si
this->special_side_idx = char(special_side_idx);
}
inline bool is_point_inside_triangle(const Vec3f &pt, const Vec3f &p1, const Vec3f &p2, const Vec3f &p3)
// Pre-computed barycentric resolver
// Real-time collision detection, Ericson, Chapter 3.4
// Cache inspired by Don Hatch at https://gamedev.stackexchange.com/questions/23743/whats-the-most-efficient-way-to-find-barycentric-coordinates/23745#comment390123_23745
struct Barycentric
{
// Real-time collision detection, Ericson, Chapter 3.4
auto barycentric = [&pt, &p1, &p2, &p3]() -> Vec3f {
std::array<Vec3f, 3> v = {p2 - p1, p3 - p1, pt - p1};
float d00 = v[0].dot(v[0]);
float d01 = v[0].dot(v[1]);
float d11 = v[1].dot(v[1]);
float d20 = v[2].dot(v[0]);
float d21 = v[2].dot(v[1]);
float denom = d00 * d11 - d01 * d01;
public:
Barycentric(const Vec3f& a, const Vec3f& b, const Vec3f& c)
:a_(a)
{
// Pre-compute denominator
const Vec3f v0 = b - a;
const Vec3f v1 = c - a;
const float d00 = v0.dot(v0);
const float d01 = v0.dot(v1);
const float d11 = v1.dot(v1);
const float inv_denom_ = 1.0f / (d00 * d11 - d01 * d01);
x1_ = (d11 * v0 - d01 * v1) * inv_denom_;
x2_ = (d00 * v1 - d01 * v0) * inv_denom_;
}
Vec3f barycentric_cords(1.f, (d11 * d20 - d01 * d21) / denom, (d00 * d21 - d01 * d20) / denom);
barycentric_cords.x() = barycentric_cords.x() - barycentric_cords.y() - barycentric_cords.z();
return barycentric_cords;
};
Vec3f calc(const Vec3f& p) const
{
const Vec3f v2 = p - a_;
const float v = v2.dot(x1_);
const float w = v2.dot(x2_);
const float u = 1.f - v - w;
Vec3f barycentric_cords = barycentric();
return {u, v, w};
}
bool is_point_inside_triangle(const Vec3f& p) const
{
const Vec3f barycentric_cords = calc(p);
return std::all_of(begin(barycentric_cords), end(barycentric_cords), [](float cord) { return 0.f <= cord && cord <= 1.0; });
}
static Vec3f calc(const Vec3f& pt, const Vec3f& p1, const Vec3f& p2, const Vec3f& p3)
{
const std::array<Vec3f, 3> vec = {p2 - p1, p3 - p1, pt - p1};
const float d00 = vec[0].dot(vec[0]);
const float d01 = vec[0].dot(vec[1]);
const float d11 = vec[1].dot(vec[1]);
const float d20 = vec[2].dot(vec[0]);
const float d21 = vec[2].dot(vec[1]);
const float denom = d00 * d11 - d01 * d01;
const float v = (d11 * d20 - d01 * d21) / denom;
const float w = (d00 * d21 - d01 * d20) / denom;
const float u = 1.f - v - w;
return {u, v, w};
}
private:
Vec3f a_;
Vec3f x1_;
Vec3f x2_;
};
static bool is_point_inside_triangle(const Vec3f &pt, const Vec3f &p1, const Vec3f &p2, const Vec3f &p3)
{
Vec3f barycentric_cords = Barycentric::calc(pt, p1, p2, p3);
return std::all_of(begin(barycentric_cords), end(barycentric_cords), [](float cord) { return 0.f <= cord && cord <= 1.0; });
}
@@ -233,7 +280,7 @@ int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) c
return this->select_unsplit_triangle(hit, facet_idx, neighbors);
}
void TriangleSelector::select_patch(int facet_start, std::unique_ptr<Cursor> &&cursor, EnforcerBlockerType new_state, const Transform3d& trafo_no_translate, bool triangle_splitting, float highlight_by_angle_deg)
void TriangleSelector::select_patch(int facet_start, std::unique_ptr<Cursor> &&cursor, EnforcerBlockerType new_state, const Transform3d& trafo_no_translate, bool triangle_splitting, float highlight_by_angle_deg, const bool select_partially)
{
assert(facet_start < m_orig_size_indices);
@@ -296,7 +343,7 @@ void TriangleSelector::select_patch(int facet_start, std::unique_ptr<Cursor> &&c
Matrix3f normal_matrix = static_cast<Matrix3f>(trafo_no_translate.matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>());
float world_normal_z = (normal_matrix* facet_normal).normalized().z();
if (!visited[facet] && (highlight_by_angle_deg == 0.f || world_normal_z < highlight_angle_limit)) {
if (select_triangle(facet, new_state, triangle_splitting)) {
if (select_triangle(facet, new_state, triangle_splitting, select_partially)) {
// add neighboring facets to list to be processed later
for (int neighbor_idx : m_neighbors[facet])
if (neighbor_idx >= 0 && m_cursor->is_facet_visible(neighbor_idx, m_face_normals))
@@ -552,7 +599,7 @@ void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_
// This is done by an actual recursive call. Returns false if the triangle is
// outside the cursor.
// Called by select_patch() and by itself.
bool TriangleSelector::select_triangle(int facet_idx, EnforcerBlockerType type, bool triangle_splitting)
bool TriangleSelector::select_triangle(int facet_idx, EnforcerBlockerType type, bool triangle_splitting, bool select_partially)
{
assert(facet_idx < int(m_triangles.size()));
@@ -562,7 +609,7 @@ bool TriangleSelector::select_triangle(int facet_idx, EnforcerBlockerType type,
Vec3i32 neighbors = m_neighbors[facet_idx];
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors));
if (! select_triangle_recursive(facet_idx, neighbors, type, triangle_splitting))
if (! select_triangle_recursive(facet_idx, neighbors, type, triangle_splitting, select_partially))
return false;
// In case that all children are leafs and have the same state now,
@@ -902,7 +949,7 @@ Vec3i32 TriangleSelector::child_neighbors_propagated(const Triangle &tr, const V
return out;
}
bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i32 &neighbors, EnforcerBlockerType type, bool triangle_splitting)
bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i32 &neighbors, EnforcerBlockerType type, bool triangle_splitting, bool select_partially)
{
assert(facet_idx < int(m_triangles.size()));
@@ -935,8 +982,10 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i32 &n
if (triangle_splitting)
split_triangle(facet_idx, neighbors);
else if (!m_triangles[facet_idx].is_split())
if ((!triangle_splitting || select_partially) && !m_triangles[facet_idx].is_split()) {
m_triangles[facet_idx].set_state(type);
return true;
}
tr = &m_triangles[facet_idx]; // might have been invalidated by split_triangle().
int num_of_children = tr->number_of_split_sides() + 1;
@@ -946,7 +995,7 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i32 &n
assert(tr->children[i] < int(m_triangles.size()));
// Recursion, deep first search over the children of this triangle.
// All children of this triangle were created by splitting a single source triangle of the original mesh.
select_triangle_recursive(tr->children[i], this->child_neighbors(*tr, neighbors, i), type, triangle_splitting);
select_triangle_recursive(tr->children[i], this->child_neighbors(*tr, neighbors, i), type, triangle_splitting, select_partially);
tr = &m_triangles[facet_idx]; // might have been invalidated
}
}
@@ -2208,4 +2257,271 @@ std::vector<EnforcerBlockerType> TriangleSelector::extract_used_facet_states(con
return out;
}
static bool segments_intersect_proj(const Vec3f& p1, const Vec3f& p2, const Vec3f& p3, const Vec3f& p4, const std::pair<int, int>& proj)
{
auto cross2d = [](float ax, float ay, float bx, float by) -> float { return ax * by - ay * bx; };
const auto [u_axis, v_axis] = proj;
const auto u1 = p1(u_axis), v1 = p1(v_axis);
const auto u2 = p2(u_axis), v2 = p2(v_axis);
const auto u3 = p3(u_axis), v3 = p3(v_axis);
const auto u4 = p4(u_axis), v4 = p4(v_axis);
const float ru = u2 - u1, rv = v2 - v1;
const float su = u4 - u3, sv = v4 - v3;
const float denom = cross2d(ru, rv, su, sv);
if (std::abs(denom) < 1e-10f)
return false;
const float dpu = u3 - u1, dpv = v3 - v1;
const float t1 = cross2d(dpu, dpv, su, sv) / denom;
const float t2 = cross2d(dpu, dpv, ru, rv) / denom;
return 0.f <= t1 && t1 <= 1.0f && 0.f <= t2 && t2 <= 1.f;
};
class TriangleCursor : public TriangleSelector::SinglePointCursor
{
public:
TriangleCursor() = delete;
explicit TriangleCursor(const Vec3f& center_,
const Vec3f& source_,
float radius_world,
const Transform3d& trafo_,
const TriangleSelector::ClippingPlane& clipping_plane_,
const std::array<const Vec3f, 3>& vertices)
: TriangleSelector::SinglePointCursor(center_, source_, radius_world, trafo_, clipping_plane_)
, barycentric_(vertices[0], vertices[1], vertices[2])
{}
~TriangleCursor() override = default;
static std::unique_ptr<Cursor> build_cursor(const TriangleSelector& source_selector, const TriangleSelector::Triangle& tri)
{
const Vec3f& pv0 = source_selector.m_vertices[tri.verts_idxs[0]].v;
const Vec3f& pv1 = source_selector.m_vertices[tri.verts_idxs[1]].v;
const Vec3f& pv2 = source_selector.m_vertices[tri.verts_idxs[2]].v;
// Calculate the centroid of the triangle
const Vec3f center = (pv0 + pv1 + pv2) / 3.f;
// Calculate the norm of the plane
const Vec3f& norm = source_selector.m_face_normals[tri.source_triangle];
// Calculate the min distance from the centroid to every edges
const float radius = std::max(std::min(std::min(point_to_line_dist(center, pv0, pv1), point_to_line_dist(center, pv0, pv2)),
point_to_line_dist(center, pv1, pv2)), 0.1f);
return std::make_unique<TriangleCursor>(center, center + norm, radius, Transform3d::Identity(), TriangleSelector::ClippingPlane(),
std::array<const Vec3f, 3>{pv0, pv1, pv2});
}
bool is_mesh_point_inside(const Vec3f& point) const override
{
return barycentric_.is_point_inside_triangle(point);
}
bool is_edge_inside_cursor(const TriangleSelector::Triangle& tr, const std::vector<TriangleSelector::Vertex>& vertices) const override
{
std::array<Vec3f, 3> pts_proj; // Projected point onto the plane
std::array<float, 3> pts_dist; // Distance to the plane, positive means above, negative means below the plane
for (int i = 0; i < 3; ++i) {
Vec3f p = vertices[tr.verts_idxs[i]].v;
if (!this->uniform_scaling)
p = this->trafo * p;
const Vec3f diff = p - center;
pts_dist[i] = diff.dot(dir);
pts_proj[i] = p - pts_dist[i] * dir;
}
for (int side = 0; side < 3; ++side) {
const int idx_a = side;
const int idx_b = side < 2 ? side + 1 : 0;
// If both ends at the same side and farther than tolerance, skip
const float dist_a = pts_dist[idx_a];
const float dist_b = pts_dist[idx_b];
if ((dist_a > tolerance && dist_b > tolerance) || (dist_a < -tolerance && dist_b < -tolerance)) {
continue;
}
// Find the projected line segment which has distance to the plane within the tolerance
Vec3f pt_a = pts_proj[idx_a];
Vec3f pt_b = pts_proj[idx_b];
if (std::abs(dist_a) > tolerance) {
pt_a = (tolerance - dist_b) / (dist_a - dist_b) * (pts_proj[idx_a] - pts_proj[idx_b]) + pts_proj[idx_b];
}
if (std::abs(dist_b) > tolerance) {
pt_b = (tolerance - dist_a) / (dist_b - dist_a) * (pts_proj[idx_b] - pts_proj[idx_a]) + pts_proj[idx_a];
}
// If any projected end is inside the triangle, then is in
if (barycentric_.is_point_inside_triangle(pt_a) ||
barycentric_.is_point_inside_triangle(pt_b)) {
return true;
}
// Otherwise see if the segment (pt_a, pt_b) intersects the triangle
{
const Vec3f uvw_a = barycentric_.calc(pt_a);
const Vec3f uvw_b = barycentric_.calc(pt_b);
const Vec3f uvw_0 {1.f,0.f,0.f};
const Vec3f uvw_1 {0.f,1.f,0.f};
const Vec3f uvw_2 {0.f,0.f,1.f};
constexpr std::pair<int, int> proj{0, 1};
if (segments_intersect_proj(uvw_a, uvw_b, uvw_0, uvw_1, proj)||
segments_intersect_proj(uvw_a, uvw_b, uvw_0, uvw_2, proj)||
segments_intersect_proj(uvw_a, uvw_b, uvw_1, uvw_2, proj)) {
return true;
}
}
}
return false;
}
bool is_facet_visible(int facet_idx, const std::vector<Vec3f>& face_normals) const override
{
const Vec3f& n = face_normals[facet_idx];
return check_normal(n, this->dir);
}
static bool check_normal(const Vec3f& facet_norm, const Vec3f& camera_dir)
{
const float a = -(facet_norm.dot(camera_dir));
return std::clamp(a, 0.f, 1.f) >= facet_angle_limit;
}
static constexpr float tolerance = 0.01f;
private:
const Barycentric barycentric_;
static const double facet_angle_limit;
static float point_to_line_dist(const Vec3f& p, const Vec3f& a, const Vec3f& b)
{
const Eigen::ParametrizedLine<float, 3> line = Eigen::ParametrizedLine<float, 3>::Through(a, b);
return line.distance(p);
}
};
const double TriangleCursor::facet_angle_limit = cos(Geometry::deg2rad(5.0));
// Remap painting data from source mesh to target mesh using spatial mapping.
TriangleSelector::TriangleSplittingData TriangleSelector::remap_painting(
const indexed_triangle_set& source_its,
const TriangleSplittingData& source_painting,
const indexed_triangle_set& target_its,
const Transform3d& target_transform,
const std::optional<std::reference_wrapper<const TriangleSplittingData>>& existing_painting)
{
TriangleSelector::TriangleSplittingData result;
if (source_painting.bitstream.empty())
return result;
// 1. Deserialize source painting
TriangleMesh source_mesh(source_its);
TriangleSelector source_selector(source_mesh);
source_selector.deserialize(source_painting, false);
// 2. Extract painted geometry
std::vector<std::reference_wrapper<const Triangle>> painted_triangles;
painted_triangles.reserve(source_selector.m_triangles.size());
for (const Triangle& tr : source_selector.m_triangles) {
if (tr.valid() && !tr.is_split() && tr.get_state() != EnforcerBlockerType::NONE) {
painted_triangles.push_back(std::ref(tr));
}
}
if (painted_triangles.empty())
return result;
// 3. Build AABB tree of target mesh so we could find nearest face quickly
TriangleMesh target_mesh(target_its);
target_mesh.transform(target_transform);
AABBTreeIndirect::Tree3f target_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(target_mesh.its.vertices, target_mesh.its.indices);
// Helper: check overlap between a paint triangle and a target triangle.
// Uses 3D barycentric point-in-triangle tests and dominant-axis 2D projection
// for edge-edge intersection to handle all triangle orientations correctly.
auto check_overlap = [&](const Vec3f& pa, const Vec3f& pb, const Vec3f& pc,
const Vec3f& ta, const Vec3f& tb, const Vec3f& tc) -> bool {
// Check if any target vertex is inside the paint triangle
if (is_point_inside_triangle(ta, pa, pb, pc)) return true;
if (is_point_inside_triangle(tb, pa, pb, pc)) return true;
if (is_point_inside_triangle(tc, pa, pb, pc)) return true;
// Check if any paint vertex is inside the target triangle
if (is_point_inside_triangle(pa, ta, tb, tc)) return true;
if (is_point_inside_triangle(pb, ta, tb, tc)) return true;
if (is_point_inside_triangle(pc, ta, tb, tc)) return true;
// Check edge-edge intersections using dominant-axis 2D projection.
// Project onto the plane (XY, XZ, or YZ) where the triangles have the
// most area, avoiding degenerate projections for vertical/angled surfaces.
Vec3f n1 = (pb - pa).cross(pc - pa);
Vec3f n2 = (tb - ta).cross(tc - ta);
Vec3f n_abs = (n1.cwiseAbs() + n2.cwiseAbs());
int axis = (n_abs.x() >= n_abs.y() && n_abs.x() >= n_abs.z()) ? 0
: (n_abs.y() >= n_abs.z()) ? 1 : 2;
const int u_axis = (axis + 1) % 3;
const int v_axis = (axis + 2) % 3;
const std::pair<int, int> proj{u_axis, v_axis};
if (segments_intersect_proj(pa, pb, ta, tb, proj)) return true;
if (segments_intersect_proj(pa, pb, tb, tc, proj)) return true;
if (segments_intersect_proj(pa, pb, tc, ta, proj)) return true;
if (segments_intersect_proj(pb, pc, ta, tb, proj)) return true;
if (segments_intersect_proj(pb, pc, tb, tc, proj)) return true;
if (segments_intersect_proj(pb, pc, tc, ta, proj)) return true;
if (segments_intersect_proj(pc, pa, ta, tb, proj)) return true;
if (segments_intersect_proj(pc, pa, tb, tc, proj)) return true;
if (segments_intersect_proj(pc, pa, tc, ta, proj)) return true;
return false;
};
// 4. For each painted face, we find the nearest target face, and apply the TriangleCursor to paint it
TriangleSelector target_selector(target_mesh);
if (existing_painting) {
// Restore existing painting first, if given
target_selector.deserialize(existing_painting->get(), false);
}
for (auto tri_ref : painted_triangles) {
const Triangle& tri = tri_ref.get();
const Vec3f& pv0 = source_selector.m_vertices[tri.verts_idxs[0]].v;
const Vec3f& pv1 = source_selector.m_vertices[tri.verts_idxs[1]].v;
const Vec3f& pv2 = source_selector.m_vertices[tri.verts_idxs[2]].v;
// Find ALL target faces whose bounding boxes overlap with the paint
// triangle's bounding box, not just the nearest one.
Eigen::AlignedBox3f pt_bbox;
pt_bbox.extend(pv0);
pt_bbox.extend(pv1);
pt_bbox.extend(pv2);
pt_bbox.min() -= Eigen::Vector3f::Constant(TriangleCursor::tolerance);
pt_bbox.max() += Eigen::Vector3f::Constant(TriangleCursor::tolerance);
AABBTreeIndirect::traverse(target_tree, AABBTreeIndirect::intersecting(pt_bbox), [&](const AABBTreeIndirect::Tree3f::Node& node) -> bool {
size_t face_idx = node.idx;
if (face_idx >= target_mesh.its.indices.size())
return true;
const Vec3f& norm_a = source_selector.m_face_normals[tri.source_triangle];
const Vec3f& norm_b = target_selector.m_face_normals[face_idx];
const Vec3i32& face = target_mesh.its.indices[face_idx];
const Vec3f& ta = target_mesh.its.vertices[face(0)];
const Vec3f& tb = target_mesh.its.vertices[face(1)];
const Vec3f& tc = target_mesh.its.vertices[face(2)];
if (TriangleCursor::check_normal(norm_b, -norm_a) && check_overlap(pv0, pv1, pv2, ta, tb, tc)) {
// Paint this face
target_selector.select_patch(face_idx, TriangleCursor::build_cursor(source_selector, tri), tri.get_state(),
Transform3d::Identity(), true, 0.f, true);
}
return true; // continue traversal
});
}
return target_selector.serialize();
}
} // namespace Slic3r
+25 -6
View File
@@ -308,7 +308,8 @@ public:
EnforcerBlockerType new_state, // enforcer or blocker?
const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation
bool triangle_splitting, // If triangles will be split base on the cursor or not
float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees.
float highlight_by_angle_deg = 0.f, // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees.
bool select_partially = false); // Select a triangle if it's partially in the cursor but too small to be subdivided
void seed_fill_select_triangles(const Vec3f &hit, // point where to start
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
@@ -372,6 +373,25 @@ public:
// The operation may merge split triangles if they are being assigned the same color.
void seed_fill_apply_on_triangles(EnforcerBlockerType new_state);
// Saved painting data for remapping after mesh change.
struct SavedPainting {
TriangleMesh mesh; // Original mesh
TriangleSplittingData supported;
TriangleSplittingData seam;
TriangleSplittingData mmu;
TriangleSplittingData fuzzy;
};
// Remap painting data from source mesh to target mesh using spatial mapping.
// `target_transform` should transform the target mesh into source's coordinate space.
// If `existing_painting` is present, the result will be a combine of `existing_painting` and remapped `source_painting`.
static TriangleSplittingData remap_painting(
const indexed_triangle_set& source_its,
const TriangleSplittingData& source_painting,
const indexed_triangle_set& target_its,
const Transform3d& target_transform,
const std::optional<std::reference_wrapper<const TriangleSplittingData>>& existing_painting);
protected:
// Triangle and info about how it's split.
class Triangle {
@@ -477,8 +497,8 @@ protected:
// Private functions:
private:
bool select_triangle(int facet_idx, EnforcerBlockerType type, bool triangle_splitting);
bool select_triangle_recursive(int facet_idx, const Vec3i32 &neighbors, EnforcerBlockerType type, bool triangle_splitting);
bool select_triangle(int facet_idx, EnforcerBlockerType type, bool triangle_splitting, bool select_partially);
bool select_triangle_recursive(int facet_idx, const Vec3i32 &neighbors, EnforcerBlockerType type, bool triangle_splitting, bool select_partially);
void undivide_triangle(int facet_idx);
void split_triangle(int facet_idx, const Vec3i32 &neighbors);
void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant.
@@ -521,11 +541,10 @@ private:
int m_free_triangles_head { -1 };
int m_free_vertices_head { -1 };
friend class TriangleCursor;
};
} // namespace Slic3r
#endif // libslic3r_TriangleSelector_hpp_
+6
View File
@@ -331,6 +331,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Mouse3DController.hpp
GUI/MsgDialog.cpp
GUI/MsgDialog.hpp
GUI/MultiChoiceDialog.hpp
GUI/MultiChoiceDialog.cpp
GUI/MultiMachine.cpp
GUI/MultiMachine.hpp
GUI/MultiMachineManagerPage.cpp
@@ -498,6 +500,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Widgets/Button.hpp
GUI/Widgets/CheckBox.cpp
GUI/Widgets/CheckBox.hpp
GUI/Widgets/CheckList.cpp
GUI/Widgets/CheckList.hpp
GUI/Widgets/ComboBox.cpp
GUI/Widgets/ComboBox.hpp
GUI/Widgets/DialogButtons.cpp
@@ -685,6 +689,8 @@ if (APPLE)
GUI/Mouse3DHandlerMac.mm
GUI/InstanceCheckMac.mm
GUI/InstanceCheckMac.h
GUI/DeepLinkHandlerMac.mm
GUI/DeepLinkHandlerMac.h
GUI/GUI_UtilsMac.mm
GUI/wxMediaCtrl2.mm
GUI/wxMediaCtrl2.h
+10 -10
View File
@@ -58,7 +58,7 @@ CenteredTitle::CenteredTitle(wxWindow* parent)
wxRect rect = GetClientRect();
wxString ellipsized = wxControl::Ellipsize(m_title, dc, wxELLIPSIZE_END, wxMax(0, rect.GetWidth() - FromDIP(8)));
int y = rect.y + (rect.height - textHeight) / 2;
int y = rect.y + (rect.height - textHeight) / 2 + 1;
int x = rect.x + ((ellipsized != m_title) // is ellipsized
? FromDIP(4) // align to left when clipped
: (rect.width - dc.GetTextExtent(m_title).GetWidth()) / 2); // centered when has available space
@@ -115,15 +115,14 @@ void BBLTopbarArt::DrawBackground(wxDC& dc, wxWindow* wnd, const wxRect& rect)
void BBLTopbarArt::DrawButton(wxDC& dc, wxWindow* wnd, const wxAuiToolBarItem& item, const wxRect& rect)
{
int textWidth = 0, textHeight = 0;
wxFontMetrics fm;
if (m_flags & wxAUI_TB_TEXT)
{
dc.SetFont(m_font);
int tx, ty;
dc.GetTextExtent(wxT("ABCDHgj"), &tx, &textHeight);
textWidth = 0;
dc.GetTextExtent(item.GetLabel(), &textWidth, &ty);
fm = dc.GetFontMetrics();
textHeight = fm.ascent + fm.descent;
dc.GetTextExtent(item.GetLabel(), &textWidth, nullptr);
}
int bmpX = 0, bmpY = 0;
@@ -158,9 +157,7 @@ void BBLTopbarArt::DrawButton(wxDC& dc, wxWindow* wnd, const wxAuiToolBarItem& i
(bmpSize.y / 2);
textX = bmpX + wnd->FromDIP(3) + bmpSize.x;
textY = rect.y +
(rect.height / 2) -
(textHeight / 2);
textY = bmpY + bmpSize.y / 2 - textHeight / 2 - fm.externalLeading + 1;
}
@@ -228,7 +225,9 @@ BBLTopbar::BBLTopbar(wxWindow* pwin, wxFrame* parent)
void BBLTopbar::Init(wxFrame* parent)
{
SetArtProvider(new BBLTopbarArt());
auto* art = new BBLTopbarArt();
art->SetFont(Label::Body_12);
SetArtProvider(art);
m_frame = parent;
m_skip_popup_file_menu = false;
m_skip_popup_dropdown_menu = false;
@@ -321,6 +320,7 @@ void BBLTopbar::Init(wxFrame* parent)
//this->AddStretchSpacer(1);
m_title_ctrl = new CenteredTitle(this);
m_title_ctrl->SetFont(Label::Body_12);
wxAuiToolBarItem* title_item = this->AddControl(m_title_ctrl, "");
title_item->SetProportion(1);
+4
View File
@@ -605,6 +605,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
bool have_multiline_infill_pattern = pattern == ipGyroid || pattern == ipGrid || pattern == ipRectilinear || pattern == ipTpmsD || pattern == ipTpmsFK || pattern == ipCrossHatch || pattern == ipHoneycomb || pattern == ipLateralLattice || pattern == ipLateralHoneycomb || pattern == ipConcentric ||
pattern == ipCubic || pattern == ipStars || pattern == ipAlignedRectilinear || pattern == ipLightning || pattern == ip3DHoneycomb || pattern == ipAdaptiveCubic || pattern == ipSupportCubic|| pattern == ipTriangles || pattern == ipQuarterCubic|| pattern == ipArchimedeanChords || pattern == ipHilbertCurve || pattern == ipOctagramSpiral;
// gyroid_optimized only applies when the sparse infill pattern is gyroid;
// hide the whole line otherwise.
toggle_line("gyroid_optimized", have_infill && pattern == ipGyroid);
// If there is infill, enable/disable fill_multiline according to whether the pattern supports multiline infill.
if (have_infill) {
toggle_field("fill_multiline", have_multiline_infill_pattern);
+16
View File
@@ -0,0 +1,16 @@
#ifndef slic3r_GUI_DeepLinkHandlerMac_h_
#define slic3r_GUI_DeepLinkHandlerMac_h_
namespace Slic3r {
namespace GUI {
// Re-registers a Cocoa Apple Event handler for kInternetEventClass/kAEGetURL.
// Works around a regression observed after upgrading to wxWidgets 3.3.2 on
// macOS Tahoe (#13119) where wxWidgets' built-in handler is registered but
// never fires for orcaslicer:// deep links.
void register_mac_deep_link_handler();
} // namespace GUI
} // namespace Slic3r
#endif
+41
View File
@@ -0,0 +1,41 @@
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
#include <boost/log/trivial.hpp>
#include "DeepLinkHandlerMac.h"
#include "GUI_App.hpp"
@interface OrcaDeepLinkHandler : NSObject
- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)reply;
@end
@implementation OrcaDeepLinkHandler
- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)reply
{
NSString *url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
if (url == nil || url.length == 0)
return;
BOOST_LOG_TRIVIAL(info) << "Deep link received: " << [url UTF8String];
Slic3r::GUI::wxGetApp().MacOpenURL(wxString::FromUTF8([url UTF8String]));
}
@end
namespace Slic3r {
namespace GUI {
void register_mac_deep_link_handler()
{
static OrcaDeepLinkHandler *handler = nil;
if (handler == nil)
handler = [[OrcaDeepLinkHandler alloc] init];
[[NSAppleEventManager sharedAppleEventManager]
setEventHandler:handler
andSelector:@selector(handleGetURLEvent:withReplyEvent:)
forEventClass:kInternetEventClass
andEventID:kAEGetURL];
}
} // namespace GUI
} // namespace Slic3r
+31 -9
View File
@@ -1,4 +1,7 @@
#include <nlohmann/json.hpp>
#include <exception>
#include "DevManager.h"
#include "DevUtil.h"
@@ -151,11 +154,15 @@ namespace Slic3r
{
keep_alive();
MachineObject* obj = this->get_selected_machine();
if (!obj) {
BOOST_LOG_TRIVIAL(warning) << "DeviceManager::check_pushing selected machine not found";
return;
}
std::chrono::system_clock::time_point start = std::chrono::system_clock::now();
auto internal = std::chrono::duration_cast<std::chrono::milliseconds>(start - obj->last_update_time);
if (obj && !obj->is_support_mqtt_alive)
if (!obj->is_support_mqtt_alive)
{
if (internal.count() > TIMEOUT_FOR_STRAT && internal.count() < 1000 * 60 * 60 * 300)
{
@@ -916,18 +923,33 @@ namespace Slic3r
const auto cloud_provider = Slic3r::GUI::wxGetApp().get_printer_cloud_provider();
if (Slic3r::GUI::wxGetApp().is_user_login(cloud_provider))
{
m_manager->check_pushing();
try
{
agent->refresh_connection(cloud_provider);
try {
m_manager->check_pushing();
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "DeviceManagerRefresher::on_timer check_pushing exception="
<< e.what();
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "DeviceManagerRefresher::on_timer check_pushing unknown exception";
}
catch (...)
{
;
try {
agent->refresh_connection(cloud_provider);
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "DeviceManagerRefresher::on_timer refresh_connection exception="
<< e.what();
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "DeviceManagerRefresher::on_timer refresh_connection unknown exception";
}
}
// certificate
agent->install_device_cert(obj->get_dev_id(), obj->is_lan_mode_printer());
try {
agent->install_device_cert(obj->get_dev_id(), obj->is_lan_mode_printer());
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "DeviceManagerRefresher::on_timer install_device_cert exception="
<< e.what();
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "DeviceManagerRefresher::on_timer install_device_cert unknown exception";
}
}
}
+2 -2
View File
@@ -175,7 +175,7 @@ void DeviceErrorDialog::init_button_list()
init_button(PROBLEM_SOLVED_RESUME, _L("Problem Solved and Resume"));
init_button(TURN_OFF_FIRE_ALARM, _L("Got it, Turn off the Fire Alarm."));
init_button(RETRY_PROBLEM_SOLVED, _L("Retry (problem solved)"));
init_button(CANCLE, _L("Cancel"));
init_button(CANCEL, _L("Cancel"));
init_button(STOP_DRYING, _L("Stop Drying"));
init_button(PROCEED, _L("Proceed"));
init_button(DBL_CHECK_CANCEL, _L("Cancel"));
@@ -445,7 +445,7 @@ void DeviceErrorDialog::on_button_click(ActionButton btn_id)
m_obj->command_ams_control("resume");
break;
}
case DeviceErrorDialog::CANCLE: {
case DeviceErrorDialog::CANCEL: {
break;
}
case DeviceErrorDialog::STOP_DRYING: {
+1 -1
View File
@@ -43,7 +43,7 @@ public:
RETRY_PROBLEM_SOLVED = 34,
STOP_DRYING = 35,
CANCLE = 37,
CANCEL = 37,
REMOVE_CLOSE_BTN = 39, // special case, do not show close button
PROCEED = 41,
+14 -3
View File
@@ -324,9 +324,18 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR
else
c_editor->SetSelection(atoi(data.GetText().c_str()) - 1);
// Open the dropdown immediately when the editor is focused.
c_editor->Bind(wxEVT_SET_FOCUS, [c_editor](wxFocusEvent& evt) {
#ifdef __WXGTK__
// On wxGTK the data-view editor may receive focus before its native
// window is mapped. Opening the popup one event later avoids creating
// the GTK popup without a valid toplevel parent.
c_editor->CallAfter([c_editor]() {
if (c_editor->IsShownOnScreen())
c_editor->ForceDropdownOpen();
});
#else
c_editor->ForceDropdownOpen();
#endif
evt.Skip();
});
@@ -343,7 +352,10 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR
bool BitmapChoiceRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value)
{
::ComboBox*c = static_cast<::ComboBox *>(ctrl);
auto* c = dynamic_cast<::ComboBox*>(ctrl);
if (!c)
return false;
int selection = c->GetSelection();
if (selection < 0)
return false;
@@ -389,4 +401,3 @@ wxSize TextRenderer::GetSize() const
return GetTextExtent(m_value);
}
+9
View File
@@ -28,6 +28,15 @@ public:
m_bmp(other.m_bmp)
{ }
DataViewBitmapText& operator=(const DataViewBitmapText &other)
{
if (this != &other) {
m_text = other.m_text;
m_bmp = other.m_bmp;
}
return *this;
}
void SetText(const wxString &text) { m_text = text; }
wxString GetText() const { return m_text; }
void SetBitmap(const wxBitmap &bmp) { m_bmp = bmp; }
+2 -2
View File
@@ -215,7 +215,7 @@ FilamentMapDialog::FilamentMapDialog(wxWindow *parent,
main_sizer->Add(bottom_panel, 0, wxEXPAND);
m_ok_btn->Bind(wxEVT_BUTTON, &FilamentMapDialog::on_ok, this);
m_cancel_btn->Bind(wxEVT_BUTTON, &FilamentMapDialog::on_cancle, this);
m_cancel_btn->Bind(wxEVT_BUTTON, &FilamentMapDialog::on_cancel, this);
SetEscapeId(wxID_CANCEL);
Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& e) {
if (e.GetKeyCode() == WXK_ESCAPE) {
@@ -286,7 +286,7 @@ void FilamentMapDialog::on_ok(wxCommandEvent &event)
EndModal(wxID_OK);
}
void FilamentMapDialog::on_cancle(wxCommandEvent &event) { EndModal(wxID_CANCEL); }
void FilamentMapDialog::on_cancel(wxCommandEvent &event) { EndModal(wxID_CANCEL); }
void FilamentMapDialog::update_panel_status(PageType page)
{
+1 -1
View File
@@ -60,7 +60,7 @@ public:
void set_modal_btn_labels(const wxString& left_label, const wxString& right_label);
private:
void on_ok(wxCommandEvent &event);
void on_cancle(wxCommandEvent &event);
void on_cancel(wxCommandEvent &event);
void on_switch_mode(wxCommandEvent &event);
void on_checkbox(wxCommandEvent &event);
+257 -57
View File
@@ -46,6 +46,7 @@
#include <array>
#include <algorithm>
#include <sstream>
#include <cmath>
#include <chrono>
@@ -97,7 +98,7 @@ static std::string get_view_type_string(libvgcode::EViewType view_type)
return _u8L("Filament");
else if (view_type == libvgcode::EViewType::LayerTimeLinear)
return _u8L("Layer Time");
else if (view_type == libvgcode::EViewType::LayerTimeLogarithmic)
else if (view_type == libvgcode::EViewType::LayerTimeLogarithmic)
return _u8L("Layer Time (log)");
// ORCA: Add Pressure Advance visualization support
else if (view_type == libvgcode::EViewType::PressureAdvance)
@@ -127,6 +128,29 @@ static int find_close_layer_idx(const std::vector<double> &zs, double &z, double
return -1;
}
static std::string format_compact_weight(double value_in_grams, bool imperial_units)
{
char buffer[64];
if (imperial_units) {
::sprintf(buffer, "%.2f oz", value_in_grams / GizmoObjectManipulation::oz_to_g);
return buffer;
}
const double abs_value = value_in_grams < 0.0 ? -value_in_grams : value_in_grams;
const char* unit = "g";
double scaled_value = abs_value;
if (scaled_value >= 1000000.0) {
scaled_value /= 1000000.0;
unit = "t";
} else if (scaled_value >= 1000.0) {
scaled_value /= 1000.0;
unit = "kg";
}
::sprintf(buffer, "%s%.2f%s", value_in_grams < 0.0 ? "-" : "", scaled_value, unit);
return buffer;
}
#if ENABLE_ACTUAL_SPEED_DEBUG
int GCodeViewer::SequentialView::ActualSpeedImguiWidget::plot(const char* label, const std::array<float, 2>& frame_size)
{
@@ -1294,6 +1318,25 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
//BBS: move the id to the end of reset
m_last_result_id = gcode_result.id;
m_gcode_result = &gcode_result;
m_move_type_counts.fill(0);
for (auto& move_type_times : m_move_type_times)
move_type_times.fill(0.0f);
m_move_type_distances.fill(0.0f);
for (const GCodeProcessorResult::MoveVertex& move : gcode_result.moves) {
if (move.internal_only)
continue;
const size_t move_type = static_cast<size_t>(move.type);
if (move_type < m_move_type_counts.size()) {
++m_move_type_counts[move_type];
for (size_t mode = 0; mode < move.time.size(); ++mode)
m_move_type_times[move_type][mode] += move.time[mode];
if (move.type == EMoveType::Retract || move.type == EMoveType::Unretract)
m_move_type_distances[move_type] += std::fabs(move.delta_extruder);
else
m_move_type_distances[move_type] += move.travel_dist;
}
}
m_only_gcode_in_preview = only_gcode;
m_sequential_view.gcode_window.load_gcode(gcode_result.filename, gcode_result.lines_ends);
@@ -1456,6 +1499,29 @@ void GCodeViewer::load_as_preview(libvgcode::GCodeInputData&& data)
{
m_loaded_as_preview = true;
m_move_type_counts.fill(0);
for (auto& move_type_times : m_move_type_times)
move_type_times.fill(0.0f);
m_move_type_distances.fill(0.0f);
const size_t normal_time_mode_idx = static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal);
for (size_t i = 0; i < data.vertices.size(); ++i) {
const libvgcode::PathVertex& vertex = data.vertices[i];
const size_t move_type = static_cast<size_t>(vertex.type);
if (move_type < m_move_type_counts.size()) {
++m_move_type_counts[move_type];
for (size_t mode = 0; mode < vertex.times.size(); ++mode)
m_move_type_times[move_type][mode] += vertex.times[mode];
if (vertex.type == libvgcode::EMoveType::Retract || vertex.type == libvgcode::EMoveType::Unretract) {
m_move_type_distances[move_type] += std::fabs(vertex.feedrate) * vertex.times[normal_time_mode_idx];
} else if (i > 0) {
const float dx = vertex.position[0] - data.vertices[i - 1].position[0];
const float dy = vertex.position[1] - data.vertices[i - 1].position[1];
const float dz = vertex.position[2] - data.vertices[i - 1].position[2];
m_move_type_distances[move_type] += std::sqrt(dx * dx + dy * dy + dz * dz);
}
}
}
m_viewer.set_extrusion_role_color(libvgcode::EGCodeExtrusionRole::Skirt, { 127, 255, 127 });
m_viewer.set_extrusion_role_color(libvgcode::EGCodeExtrusionRole::ExternalPerimeter, { 255, 255, 0 });
m_viewer.set_extrusion_role_color(libvgcode::EGCodeExtrusionRole::SupportMaterial, { 127, 255, 127 });
@@ -1503,6 +1569,10 @@ void GCodeViewer::reset()
m_extruders_count = 0;
m_filament_diameters = std::vector<float>();
m_filament_densities = std::vector<float>();
m_move_type_counts.fill(0);
for (auto& move_type_times : m_move_type_times)
move_type_times.fill(0.0f);
m_move_type_distances.fill(0.0f);
m_print_statistics.reset();
m_custom_gcode_per_print_z = std::vector<CustomGCode::Item>();
m_left_extruder_filament.clear();
@@ -2979,39 +3049,42 @@ void GCodeViewer::render_all_plates_stats(const std::vector<const GCodeProcessor
columns_offsets.push_back({ std::to_string(it->first + 1), offsets[_u8L("Filament")]});
char buf[64];
double unit_conver = imperial_units ? GizmoObjectManipulation::oz_to_g : 1.0;
float column_sum_m = 0.0f;
float column_sum_g = 0.0f;
if (displayed_columns & ColumnData::Model) {
const std::string weight_text = format_compact_weight(model_used_filaments_g_all_plates[i], imperial_units);
if ((displayed_columns & ~ColumnData::Model) > 0)
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", model_used_filaments_m_all_plates[i], model_used_filaments_g_all_plates[i] / unit_conver);
::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", model_used_filaments_m_all_plates[i], weight_text.c_str());
else
::sprintf(buf, imperial_units ? "%.2f in %.2f oz" : "%.2f m %.2f g", model_used_filaments_m_all_plates[i], model_used_filaments_g_all_plates[i] / unit_conver);
::sprintf(buf, imperial_units ? "%.2f in %s" : "%.2f m %s", model_used_filaments_m_all_plates[i], weight_text.c_str());
columns_offsets.push_back({ buf, offsets[_u8L("Model")] });
column_sum_m += model_used_filaments_m_all_plates[i];
column_sum_g += model_used_filaments_g_all_plates[i];
}
if (displayed_columns & ColumnData::Support) {
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", support_used_filaments_m_all_plates[i], support_used_filaments_g_all_plates[i] / unit_conver);
const std::string weight_text = format_compact_weight(support_used_filaments_g_all_plates[i], imperial_units);
::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", support_used_filaments_m_all_plates[i], weight_text.c_str());
columns_offsets.push_back({ buf, offsets[_u8L("Support")] });
column_sum_m += support_used_filaments_m_all_plates[i];
column_sum_g += support_used_filaments_g_all_plates[i];
}
if (displayed_columns & ColumnData::Flushed) {
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", flushed_filaments_m_all_plates[i], flushed_filaments_g_all_plates[i] / unit_conver);
const std::string weight_text = format_compact_weight(flushed_filaments_g_all_plates[i], imperial_units);
::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", flushed_filaments_m_all_plates[i], weight_text.c_str());
columns_offsets.push_back({ buf, offsets[_u8L("Flushed")] });
column_sum_m += flushed_filaments_m_all_plates[i];
column_sum_g += flushed_filaments_g_all_plates[i];
}
if (displayed_columns & ColumnData::WipeTower) {
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", wipe_tower_used_filaments_m_all_plates[i], wipe_tower_used_filaments_g_all_plates[i] / unit_conver);
const std::string weight_text = format_compact_weight(wipe_tower_used_filaments_g_all_plates[i], imperial_units);
::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", wipe_tower_used_filaments_m_all_plates[i], weight_text.c_str());
columns_offsets.push_back({ buf, offsets[_u8L("Tower")] });
column_sum_m += wipe_tower_used_filaments_m_all_plates[i];
column_sum_g += wipe_tower_used_filaments_g_all_plates[i];
}
if ((displayed_columns & ~ColumnData::Model) > 0) {
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", column_sum_m, column_sum_g / unit_conver);
const std::string weight_text = format_compact_weight(column_sum_g, imperial_units);
::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", column_sum_m, weight_text.c_str());
columns_offsets.push_back({ buf, offsets[_u8L("Total")] });
}
@@ -3376,7 +3449,9 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
const PrintEstimatedStatistics::Mode& time_mode = m_print_statistics.modes[static_cast<size_t>(m_viewer.get_time_mode())];
const libvgcode::EViewType curr_view_type = m_viewer.get_view_type();
const int curr_view_type_i = static_cast<int>(curr_view_type);
bool show_estimated_time = time_mode.time > 0.0f && (curr_view_type == libvgcode::EViewType::FeatureType ||
const size_t current_time_mode = static_cast<size_t>(m_viewer.get_time_mode());
const float total_estimated_time = time_mode.time > 0.0f ? time_mode.time : m_viewer.get_estimated_time();
bool show_estimated_time = total_estimated_time > 0.0f && (curr_view_type == libvgcode::EViewType::FeatureType ||
curr_view_type == libvgcode::EViewType::LayerTimeLinear || curr_view_type == libvgcode::EViewType::LayerTimeLogarithmic ||
(curr_view_type == libvgcode::EViewType::ColorPrint && !time_mode.custom_gcode_times.empty()));
@@ -3390,6 +3465,39 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
ImVec2 pos_rect = ImGui::GetCursorScreenPos();
float window_padding = 4.0f * m_scale;
auto format_compact_count = [](unsigned long long value) {
static constexpr const char* suffixes[] = { "", "K", "M", "B", "T", "P", "E" };
constexpr size_t suffix_count = sizeof(suffixes) / sizeof(suffixes[0]);
if (value < 1000)
return std::to_string(value);
size_t suffix_index = 0;
unsigned long long divisor = 1;
while (suffix_index + 1 < suffix_count && value / divisor >= 1000) {
divisor *= 1000;
++suffix_index;
}
const unsigned long long whole = value / divisor;
const unsigned long long tenths = (value % divisor) * 10 / divisor;
std::string ret = std::to_string(whole);
if (tenths != 0)
ret += "." + std::to_string(tenths);
ret += suffixes[suffix_index];
return ret;
};
auto format_percent = [](float percent) {
if (percent == 0.0f)
return std::string("0");
char buffer[64];
percent > 0.001f ? ::sprintf(buffer, "%.1f", percent * 100.0f) : ::sprintf(buffer, "<0.1");
return std::string(buffer);
};
// ORCA dont use background on top bar to give modern look
//draw_list->AddRectFilled(ImVec2(pos_rect.x,pos_rect.y - ImGui::GetStyle().WindowPadding.y),
//ImVec2(pos_rect.x + ImGui::GetWindowWidth() + ImGui::GetFrameHeight(),pos_rect.y + ImGui::GetFrameHeight() + window_padding * 2.5),
@@ -3426,10 +3534,10 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
case EItemType::Line: {
draw_list->AddLine({ pos.x + 1, pos.y + icon_size + 2 }, { pos.x + icon_size - 1, pos.y + 4 }, ImGuiWrapper::to_ImU32(color), 3.0f);
break;
}
case EItemType::None:
break;
}
}
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(20.0 * m_scale, 6.0 * m_scale));
@@ -3601,14 +3709,26 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
return _u8L("from") + " " + std::string(buf1) + " " + _u8L("to") + " " + std::string(buf2) + " " + _u8L("mm");
};
auto role_time_and_percent = [this, time_mode](libvgcode::EGCodeExtrusionRole role) {
auto role_time_and_percent = [this, total_estimated_time](libvgcode::EGCodeExtrusionRole role) {
const float time = m_viewer.get_extrusion_role_estimated_time(role);
return std::make_pair(time, time / time_mode.time);
return std::make_pair(time, total_estimated_time > 0.0f ? time / total_estimated_time : 0.0f);
};
auto travel_time_and_percent = [this, time_mode]() {
auto travel_time_and_percent = [this, total_estimated_time]() {
const float time = m_viewer.get_travels_estimated_time();
return std::make_pair(time, time / time_mode.time);
return std::make_pair(time, total_estimated_time > 0.0f ? time / total_estimated_time : 0.0f);
};
auto format_distance = [imperial_units](float distance_mm) {
char buffer[64];
if (imperial_units) {
::sprintf(buffer, "%.2fin", distance_mm / GizmoObjectManipulation::in_to_mm);
} else if (std::fabs(distance_mm) < 1000.0f) {
::sprintf(buffer, "%.0fmm", distance_mm);
} else {
::sprintf(buffer, "%.2fm", distance_mm / 1000.0f);
}
return std::string(buffer);
};
auto used_filament_per_role = [this, imperial_units](ExtrusionRole role) {
@@ -3713,6 +3833,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
std::vector<std::string> used_filaments_length;
std::vector<std::string> used_filaments_weight;
std::string travel_percent;
std::string travel_distance;
std::string travel_moves;
std::vector<double> model_used_filaments_m;
std::vector<double> model_used_filaments_g;
double total_model_used_filament_m = 0, total_model_used_filament_g = 0;
@@ -3832,8 +3954,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
auto [model_used_filament_m, model_used_filament_g] = used_filament_per_role(convert(role));
::sprintf(buffer, imperial_units ? "%.2fin" : "%.2fm", model_used_filament_m); // ORCA dont use spacing between value and unit
used_filaments_length.push_back(buffer);
::sprintf(buffer, imperial_units ? "%.2foz" : "%.2fg", model_used_filament_g); // ORCA dont use spacing between value and unit
used_filaments_weight.push_back(buffer);
used_filaments_weight.push_back(format_compact_weight(model_used_filament_g, imperial_units));
}
}
@@ -3846,10 +3967,19 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
else
percent > 0.001 ? ::sprintf(buffer, "%.1f", percent * 100) : ::sprintf(buffer, "<0.1");
travel_percent = buffer;
percents.push_back(travel_percent);
// Set travel distance and moves for the Travel row Usage columns
travel_distance = format_distance(m_print_statistics.total_travel_distance);
used_filaments_length.push_back(travel_distance);
travel_moves = format_compact_count(m_print_statistics.total_travel_moves);
used_filaments_weight.push_back(travel_moves);
}
// ORCA use % symbol for percentage and use "Usage" for "Used filaments"
offsets = calculate_offsets({ {_u8L("Line Type"), labels}, {_u8L("Time"), times}, {"%", percents}, {"", used_filaments_length}, {"", used_filaments_weight}, {_u8L("Display"), {""}}}, icon_size);
percents.pop_back();
append_headers({{_u8L("Line Type"), offsets[0]}, {_u8L("Time"), offsets[1]}, {"%", offsets[2]}, {_u8L("Usage"), offsets[3]}, {_u8L("Display"), offsets[5]}});
break;
}
@@ -3913,7 +4043,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
{
std::vector<std::string> total_filaments;
char buffer[64];
::sprintf(buffer, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", ps.total_used_filament / /*1000*/koef, ps.total_weight / unit_conver);
const std::string total_weight_text = format_compact_weight(ps.total_weight, imperial_units);
::sprintf(buffer, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", ps.total_used_filament / /*1000*/koef, total_weight_text.c_str());
total_filaments.push_back(buffer);
@@ -3948,9 +4079,54 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
default: { break; }
}
auto append_option_item = [this, append_item](libvgcode::EOptionType type, std::vector<float> offsets) {
auto append_option_item_with_type = [this, offsets, append_item](libvgcode::EOptionType type, const ColorRGBA& color, const std::string& label, bool visible) {
append_item(EItemType::Rect, color, {{ label , offsets[0] }}, true, offsets.back()/*ORCA checkbox_pos*/, visible, [this, type, visible]() {
auto append_option_item = [this, append_item, current_time_mode, total_estimated_time, &format_compact_count, &format_percent, &format_distance](libvgcode::EOptionType type, std::vector<float> offsets) {
const bool full_layout = offsets.size() > 4;
auto option_stats = [this, current_time_mode, total_estimated_time, &format_compact_count, &format_percent, &format_distance, full_layout](libvgcode::EOptionType option_type) -> std::array<std::string, 4> {
libvgcode::EMoveType move_type;
bool has_move_type = true;
switch (option_type) {
case libvgcode::EOptionType::Wipes: { move_type = libvgcode::EMoveType::Wipe; break; }
case libvgcode::EOptionType::Retractions: { move_type = libvgcode::EMoveType::Retract; break; }
case libvgcode::EOptionType::Unretractions: { move_type = libvgcode::EMoveType::Unretract; break; }
case libvgcode::EOptionType::Seams: { move_type = libvgcode::EMoveType::Seam; break; }
case libvgcode::EOptionType::ToolChanges: { move_type = libvgcode::EMoveType::ToolChange; break; }
default: { has_move_type = false; break; }
}
if (!has_move_type)
return { "", "", "", "" };
const size_t move_type_idx = static_cast<size_t>(move_type);
float time = m_move_type_times[move_type_idx][current_time_mode];
if (option_type == libvgcode::EOptionType::ToolChanges) {
// Toolchange delays are injected via synchronize() and are not attributed to ToolChange move vertices.
time = m_print_statistics.total_filament_load_time + m_print_statistics.total_filament_unload_time + m_print_statistics.total_tool_change_time;
}
const std::string time_text = full_layout && time > 0.0f ? short_time(get_time_dhms(time)) : "";
const std::string percent_text = full_layout && total_estimated_time > 0.0f ? format_percent(time / total_estimated_time) : "";
const float seam_distance = m_print_statistics.total_seam_gap_distance + m_print_statistics.total_seam_scarf_distance;
const float distance = (option_type == libvgcode::EOptionType::Seams && seam_distance > 0.0f) ? seam_distance : m_move_type_distances[move_type_idx];
const std::string distance_text = full_layout && (option_type == libvgcode::EOptionType::Wipes || option_type == libvgcode::EOptionType::Retractions || option_type == libvgcode::EOptionType::Unretractions || option_type == libvgcode::EOptionType::Seams)
? format_distance(distance)
: "";
const std::string count_text = full_layout ? format_compact_count(m_move_type_counts[move_type_idx]) : "";
return { time_text, percent_text, distance_text, count_text };
};
auto append_option_item_with_type = [this, offsets, append_item, full_layout](libvgcode::EOptionType type, const ColorRGBA& color, const std::string& label, bool visible,
const std::string& time_text, const std::string& percent_text, const std::string& distance_text, const std::string& count_text) {
std::vector<std::pair<std::string, float>> columns_offsets;
columns_offsets.push_back({ label , offsets[0] });
if (full_layout && !time_text.empty())
columns_offsets.push_back({ time_text, offsets[1] });
if (full_layout && !percent_text.empty())
columns_offsets.push_back({ percent_text, offsets[2] });
if (full_layout && !distance_text.empty())
columns_offsets.push_back({ distance_text, offsets[3] });
if (full_layout && !count_text.empty())
columns_offsets.push_back({ count_text, distance_text.empty() ? offsets[3] : offsets[4] });
append_item(EItemType::Rect, color, columns_offsets, true, offsets.back()/*ORCA checkbox_pos*/, visible, [this, type, visible]() {
m_viewer.toggle_option_visibility(type);
update_moves_slider();
});
@@ -3958,18 +4134,33 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
const bool visible = m_viewer.is_option_visible(type);
if (type == libvgcode::EOptionType::Travels) {
//BBS: only display travel time in FeatureType view
append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Travels)), _u8L("Travel"), visible);
append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Travels)), _u8L("Travel"), visible, "", "", "", "");
}
else if (type == libvgcode::EOptionType::Seams) {
const auto option_values = option_stats(type);
append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Seams)), _u8L("Seams"), visible,
option_values[0], option_values[1], option_values[2], option_values[3]);
}
else if (type == libvgcode::EOptionType::Retractions) {
const auto option_values = option_stats(type);
append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Retractions)), _u8L("Retract"), visible,
option_values[0], option_values[1], option_values[2], option_values[3]);
}
else if (type == libvgcode::EOptionType::Unretractions) {
const auto option_values = option_stats(type);
append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Unretractions)), _u8L("Unretract"), visible,
option_values[0], option_values[1], option_values[2], option_values[3]);
}
else if (type == libvgcode::EOptionType::ToolChanges) {
const auto option_values = option_stats(type);
append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::ToolChanges)), _u8L("Filament Changes"), visible,
option_values[0], option_values[1], option_values[2], option_values[3]);
}
else if (type == libvgcode::EOptionType::Wipes) {
const auto option_values = option_stats(type);
append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Wipes)), _u8L("Wipe"), visible,
option_values[0], option_values[1], option_values[2], option_values[3]);
}
else if (type == libvgcode::EOptionType::Seams)
append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Seams)), _u8L("Seams"), visible);
else if (type == libvgcode::EOptionType::Retractions)
append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Retractions)), _u8L("Retract"), visible);
else if (type == libvgcode::EOptionType::Unretractions)
append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Unretractions)), _u8L("Unretract"), visible);
else if (type == libvgcode::EOptionType::ToolChanges)
append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::ToolChanges)), _u8L("Filament Changes"), visible);
else if (type == libvgcode::EOptionType::Wipes)
append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Wipes)), _u8L("Wipe"), visible);
};
const libvgcode::EViewType new_view_type = curr_view_type;
@@ -4009,6 +4200,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
columns_offsets.push_back({ _u8L("Travel"), offsets[0] });
columns_offsets.push_back({ travel_time, offsets[1] });
columns_offsets.push_back({ travel_percent, offsets[2] });
columns_offsets.push_back({ travel_distance, offsets[3] }); // Usage column
columns_offsets.push_back({ travel_moves, offsets[4] }); // Usage column
append_item(EItemType::Rect, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Travels)), columns_offsets, true, offsets.back()/*ORCA checkbox_pos*/, visible, [this, item, visible]() {
m_viewer.toggle_option_visibility(item);
update_moves_slider();
@@ -4100,7 +4293,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
size_t i = 0;
const std::vector<uint8_t>& used_extruders_ids = m_viewer.get_used_extruders_ids();
for (uint8_t extruder_id : used_extruders_ids) {
::sprintf(buf, imperial_units ? "%.2f in %.2f g" : "%.2f m %.2f g", model_used_filaments_m[i], model_used_filaments_g[i]);
const std::string weight_text = format_compact_weight(model_used_filaments_g[i], imperial_units);
::sprintf(buf, imperial_units ? "%.2f in %s" : "%.2f m %s", model_used_filaments_m[i], weight_text.c_str());
append_item(EItemType::Rect, libvgcode::convert(m_viewer.get_tool_colors()[extruder_id]), { { _u8L("Extruder") + " " + std::to_string(extruder_id + 1), offsets[0]}, {buf, offsets[1]} });
// append_item(EItemType::Rect, libvgcode::convert(m_viewer.get_tool_colors()[extruder_id]), _u8L("Extruder") + " " + std::to_string(extruder_id + 1),
// true, "", 0.0f, 0.0f, offsets, used_filaments_m[extruder_id], used_filaments_g[extruder_id]);
@@ -4113,7 +4307,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
char buf[64];
imgui.text(_u8L("Total") + ":");
ImGui::SameLine();
::sprintf(buf, imperial_units ? "%.2f in / %.2f oz" : "%.2f m / %.2f g", ps.total_used_filament / koef, ps.total_weight / unit_conver);
const std::string total_weight_text = format_compact_weight(ps.total_weight, imperial_units);
::sprintf(buf, imperial_units ? "%.2f in / %s" : "%.2f m / %s", ps.total_used_filament / koef, total_weight_text.c_str());
imgui.text(buf);
ImGui::Dummy({window_padding, window_padding});
@@ -4159,34 +4354,39 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
float column_sum_m = 0.0f;
float column_sum_g = 0.0f;
if (displayed_columns & ColumnData::Model) {
const std::string weight_text = format_compact_weight(model_used_filaments_g[i], imperial_units);
if ((displayed_columns & ~ColumnData::Model) > 0)
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", model_used_filaments_m[i], model_used_filaments_g[i] / unit_conver);
::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", model_used_filaments_m[i], weight_text.c_str());
else
::sprintf(buf, imperial_units ? "%.2f in %.2f oz" : "%.2f m %.2f g", model_used_filaments_m[i], model_used_filaments_g[i] / unit_conver);
::sprintf(buf, imperial_units ? "%.2f in %s" : "%.2f m %s", model_used_filaments_m[i], weight_text.c_str());
columns_offsets.push_back({ buf, color_print_offsets[_u8L("Model")] });
column_sum_m += model_used_filaments_m[i];
column_sum_g += model_used_filaments_g[i];
}
if (displayed_columns & ColumnData::Support) {
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", support_used_filaments_m[i], support_used_filaments_g[i] / unit_conver);
const std::string weight_text = format_compact_weight(support_used_filaments_g[i], imperial_units);
::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", support_used_filaments_m[i], weight_text.c_str());
columns_offsets.push_back({ buf, color_print_offsets[_u8L("Support")] });
column_sum_m += support_used_filaments_m[i];
column_sum_g += support_used_filaments_g[i];
}
if (displayed_columns & ColumnData::Flushed) {
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", flushed_filaments_m[i], flushed_filaments_g[i] / unit_conver);
const std::string weight_text = format_compact_weight(flushed_filaments_g[i], imperial_units);
::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", flushed_filaments_m[i], weight_text.c_str());
columns_offsets.push_back({ buf, color_print_offsets[_u8L("Flushed")]});
column_sum_m += flushed_filaments_m[i];
column_sum_g += flushed_filaments_g[i];
}
if (displayed_columns & ColumnData::WipeTower) {
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", wipe_tower_used_filaments_m[i], wipe_tower_used_filaments_g[i] / unit_conver);
const std::string weight_text = format_compact_weight(wipe_tower_used_filaments_g[i], imperial_units);
::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", wipe_tower_used_filaments_m[i], weight_text.c_str());
columns_offsets.push_back({ buf, color_print_offsets[_u8L("Tower")] });
column_sum_m += wipe_tower_used_filaments_m[i];
column_sum_g += wipe_tower_used_filaments_g[i];
}
if ((displayed_columns & ~ColumnData::Model) > 0) {
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", column_sum_m, column_sum_g / unit_conver);
const std::string weight_text = format_compact_weight(column_sum_g, imperial_units);
::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", column_sum_m, weight_text.c_str());
columns_offsets.push_back({ buf, color_print_offsets[_u8L("Total")] });
}
@@ -4212,27 +4412,32 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
std::vector<std::pair<std::string, float>> columns_offsets;
columns_offsets.push_back({ _u8L("Total"), color_print_offsets[_u8L("Filament")]});
if (displayed_columns & ColumnData::Model) {
const std::string weight_text = format_compact_weight(total_model_used_filament_g, imperial_units);
if ((displayed_columns & ~ColumnData::Model) > 0)
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_model_used_filament_m, total_model_used_filament_g / unit_conver);
::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", total_model_used_filament_m, weight_text.c_str());
else
::sprintf(buf, imperial_units ? "%.2f in %.2f oz" : "%.2f m %.2f g", total_model_used_filament_m, total_model_used_filament_g / unit_conver);
::sprintf(buf, imperial_units ? "%.2f in %s" : "%.2f m %s", total_model_used_filament_m, weight_text.c_str());
columns_offsets.push_back({ buf, color_print_offsets[_u8L("Model")] });
}
if (displayed_columns & ColumnData::Support) {
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_support_used_filament_m, total_support_used_filament_g / unit_conver);
const std::string weight_text = format_compact_weight(total_support_used_filament_g, imperial_units);
::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", total_support_used_filament_m, weight_text.c_str());
columns_offsets.push_back({ buf, color_print_offsets[_u8L("Support")] });
}
if (displayed_columns & ColumnData::Flushed) {
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_flushed_filament_m, total_flushed_filament_g / unit_conver);
const std::string weight_text = format_compact_weight(total_flushed_filament_g, imperial_units);
::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", total_flushed_filament_m, weight_text.c_str());
columns_offsets.push_back({ buf, color_print_offsets[_u8L("Flushed")] });
}
if (displayed_columns & ColumnData::WipeTower) {
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_wipe_tower_used_filament_m, total_wipe_tower_used_filament_g / unit_conver);
const std::string weight_text = format_compact_weight(total_wipe_tower_used_filament_g, imperial_units);
::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", total_wipe_tower_used_filament_m, weight_text.c_str());
columns_offsets.push_back({ buf, color_print_offsets[_u8L("Tower")] });
}
if ((displayed_columns & ~ColumnData::Model) > 0) {
::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_model_used_filament_m + total_support_used_filament_m + total_flushed_filament_m + total_wipe_tower_used_filament_m,
(total_model_used_filament_g + total_support_used_filament_g + total_flushed_filament_g + total_wipe_tower_used_filament_g) / unit_conver);
const std::string weight_text = format_compact_weight(total_model_used_filament_g + total_support_used_filament_g + total_flushed_filament_g + total_wipe_tower_used_filament_g, imperial_units);
::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", total_model_used_filament_m + total_support_used_filament_m + total_flushed_filament_m + total_wipe_tower_used_filament_m,
weight_text.c_str());
columns_offsets.push_back({ buf, color_print_offsets[_u8L("Total")] });
}
append_item(EItemType::None, libvgcode::convert(tool_colors[0]), columns_offsets);
@@ -4243,16 +4448,14 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
ImGui::SameLine();
imgui.text(_u8L("Filament change times") + ":");
ImGui::SameLine();
::sprintf(buf, "%d", m_print_statistics.total_filament_changes);
imgui.text(buf);
imgui.text(format_compact_count(m_print_statistics.total_filament_changes));
//display tool change times
ImGui::Dummy({window_padding, window_padding});
ImGui::SameLine();
imgui.text(_u8L("Tool changes") + ":");
ImGui::SameLine();
::sprintf(buf, "%d", m_print_statistics.total_extruder_changes);
imgui.text(buf);
imgui.text(format_compact_count(m_print_statistics.total_extruder_changes));
//BBS display cost
ImGui::Dummy({ window_padding, window_padding });
@@ -4379,8 +4582,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
imgui.text(buffer);
ImGui::SameLine(offsets[3]);
::sprintf(buffer, "%.2f g", used_filament.second);
imgui.text(buffer);
imgui.text(format_compact_weight(used_filament.second, imperial_units));
}
};
@@ -4705,8 +4907,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", ps.total_used_filament / koef);
imgui.text(buf);
ImGui::SameLine();
::sprintf(buf, imperial_units ? " %.2f oz" : " %.2f g", ps.total_weight / unit_conver);
imgui.text(buf);
imgui.text(" " + format_compact_weight(ps.total_weight, imperial_units));
ImGui::Dummy({ window_padding, window_padding });
ImGui::SameLine();
imgui.text(model_filament_str + ":");
@@ -4716,8 +4917,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", ps.total_used_filament / koef - exlude_m);
imgui.text(buf);
ImGui::SameLine();
::sprintf(buf, imperial_units ? " %.2f oz" : " %.2f g", (ps.total_weight - exlude_g) / unit_conver);
imgui.text(buf);
imgui.text(" " + format_compact_weight(ps.total_weight - exlude_g, imperial_units));
//BBS: display cost of filaments
ImGui::Dummy({ window_padding, window_padding });
ImGui::SameLine();
+4
View File
@@ -14,6 +14,7 @@
// needed for tech VGCODE_ENABLE_COG_AND_TOOL_MARKERS
#include <libvgcode/include/Types.hpp>
#include <array>
#include <cstdint>
#include <float.h>
#include <set>
@@ -186,6 +187,9 @@ private:
unsigned int m_last_result_id{ 0 };
//BBS: save m_gcode_result as well
const GCodeProcessorResult* m_gcode_result;
std::array<unsigned int, static_cast<size_t>(EMoveType::Count)> m_move_type_counts{};
std::array<std::array<float, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)>, static_cast<size_t>(EMoveType::Count)> m_move_type_times{};
std::array<float, static_cast<size_t>(EMoveType::Count)> m_move_type_distances{};
//BBS: add only gcode mode
bool m_only_gcode_in_preview {false};
+191 -17
View File
@@ -14,6 +14,7 @@
#include "libslic3r/Technologies.hpp"
#include "libslic3r/Tesselate.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/AppConfig.hpp"
#include "3DScene.hpp"
#include "BackgroundSlicingProcess.hpp"
#include "GLShader.hpp"
@@ -225,7 +226,7 @@ bool GLCanvas3D::LayersEditing::is_allowed() const
float GLCanvas3D::LayersEditing::s_overlay_window_width;
void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(const GLCanvas3D& canvas) {
void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(GLCanvas3D& canvas) {
if (!m_enabled)
return;
@@ -335,8 +336,23 @@ void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(const GLCanv
GLGizmoUtils::render_tooltip_button(&imgui, canvas, shortcuts, x, y);
ImGui::SameLine();
if (imgui.button(_L("Reset")))
wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE));
imgui.disabled_begin(check_object_layers_fixed(*m_slicing_parameters, m_layer_height_profile));
if (imgui.button(_L("Reset"))) {
wxPostEvent((wxEvtHandler*) canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE));
}
imgui.disabled_end();
ImGui::SameLine();
GLGizmoUtils::begin_right_aligned_buttons({_L("Done")});
if (imgui.button(_L("Done"))) {
m_enabled = false;
GLToolbarItem* item = canvas.m_main_toolbar.get_item("layersediting");
item->set_state(GLToolbarItem::Normal);
canvas.set_as_dirty();
canvas.request_extra_frame();
}
GLCanvas3D::LayersEditing::s_overlay_window_width = ImGui::GetWindowSize().x;
imgui.end();
@@ -344,7 +360,7 @@ void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(const GLCanv
imgui.pop_toolbar_style();
}
void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas)
void GLCanvas3D::LayersEditing::render_overlay(GLCanvas3D& canvas)
{
render_variable_layer_height_dialog(canvas);
render_active_object_annotations(canvas);
@@ -1202,6 +1218,11 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed)
GLCanvas3D::~GLCanvas3D()
{
if (m_fxaa_texture_id != 0 && _set_current()) {
glsafe(::glDeleteTextures(1, &m_fxaa_texture_id));
m_fxaa_texture_id = 0;
}
reset_volumes();
m_sel_plate_toolbar.del_all_item();
@@ -2082,9 +2103,16 @@ void GLCanvas3D::render(bool only_init)
if (m_picking_enabled && m_rectangle_selection.is_dragging())
m_rectangle_selection.render(*this);
if (_is_fxaa_enabled())
_render_fxaa_pass(static_cast<unsigned int>(cnv_size.get_width()), static_cast<unsigned int>(cnv_size.get_height()));
// draw overlays
_render_overlays();
const int current_fps = m_render_stats.get_fps_and_reset_if_needed();
if (_is_fps_overlay_enabled())
_render_fps_overlay(current_fps);
if (wxGetApp().plater()->is_render_statistic_dialog_visible()) {
ImGui::ShowMetricsWindow();
@@ -2092,7 +2120,7 @@ void GLCanvas3D::render(bool only_init)
imgui.begin(std::string("Render statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
imgui.text("FPS (SwapBuffers() calls per second):");
ImGui::SameLine();
imgui.text(std::to_string(m_render_stats.get_fps_and_reset_if_needed()));
imgui.text(std::to_string(current_fps));
ImGui::Separator();
imgui.text("Compressed textures:");
ImGui::SameLine();
@@ -3202,6 +3230,22 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
wxGetApp().imgui()->reset_requires_extra_frame();
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
const int fps_cap = _get_effective_fps_cap();
if (fps_cap > 0) {
const auto now = std::chrono::steady_clock::now();
const auto min_frame_time = std::chrono::duration<double>(1.0 / static_cast<double>(fps_cap));
const auto elapsed = now - m_last_frame_start_time;
if (elapsed < min_frame_time) {
const int wait_ms = std::max(1, static_cast<int>(std::ceil(std::chrono::duration<double, std::milli>(min_frame_time - elapsed).count())));
schedule_extra_frame(wait_ms);
evt.RequestMore();
return;
}
// Pace by frame-start interval so rendering time is part of the target budget.
m_last_frame_start_time = now;
}
_refresh_if_shown_on_screen();
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
@@ -4251,7 +4295,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
}
bool any_gizmo_active = m_gizmos.get_current() != nullptr;
bool swap_mouse_buttons = wxGetApp().app_config->get_bool("swap_mouse_buttons");
std::map<MouseButton, MouseAction> button_mappings;
button_mappings[MouseButton::Left] = static_cast<MouseAction>(std::atoi(wxGetApp().app_config->get("left_mouse_drag_action").c_str()));
button_mappings[MouseButton::Middle] = static_cast<MouseAction>(std::atoi(wxGetApp().app_config->get("middle_mouse_drag_action").c_str()));
button_mappings[MouseButton::Right] = static_cast<MouseAction>(std::atoi(wxGetApp().app_config->get("right_mouse_drag_action").c_str()));
if (m_mouse.drag.move_requires_threshold && m_mouse.is_move_start_threshold_position_2D_defined() && m_mouse.is_move_threshold_met(pos)) {
m_mouse.drag.move_requires_threshold = false;
@@ -4466,7 +4514,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_dirty = true;
}
}
else if (evt.Dragging() || is_camera_rotate(evt, swap_mouse_buttons) || is_camera_pan(evt, swap_mouse_buttons)) {
else if (evt.Dragging() || is_camera_rotate(evt, button_mappings) || is_camera_pan(evt, button_mappings)) {
m_mouse.dragging = true;
if (m_layers_editing.state != LayersEditing::Unknown && layer_editing_object_idx != -1) {
@@ -4476,10 +4524,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
}
}
// do not process the dragging if the left mouse was set down in another canvas
else if (is_camera_rotate(evt, swap_mouse_buttons)) {
else if (is_camera_rotate(evt, button_mappings)) {
// Orca: Sphere rotation for painting view
// if dragging over blank area with left button or button functions swapped then rotate
if ((any_gizmo_active || swap_mouse_buttons || m_hover_volume_idxs.empty()) && m_mouse.is_start_position_3D_defined()) {
// if dragging over blank area with left button or other button mapped to rotate, then rotate
bool middle_or_right_button_used_as_rotate = (evt.MiddleIsDown() && button_mappings[MouseButton::Middle] == MouseAction::Rotation) ||
(evt.RightIsDown() && button_mappings[MouseButton::Right] == MouseAction::Rotation);
if ((any_gizmo_active || middle_or_right_button_used_as_rotate || m_hover_volume_idxs.empty()) &&
m_mouse.is_start_position_3D_defined()) {
Camera& camera = wxGetApp().plater()->get_camera();
auto mult_pref = wxGetApp().app_config->get("camera_orbit_mult");
const double mult = mult_pref.empty() ? 1.0 : std::stod(mult_pref);
@@ -4552,7 +4603,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_camera_movement = true;
m_mouse.drag.start_position_3D = Vec3d((double)pos(0), (double)pos(1), 0.0);
}
else if (is_camera_pan(evt, swap_mouse_buttons)) {
else if (is_camera_pan(evt, button_mappings)) {
// if dragging with right button or if button functions swapped and dragging with left button over blank area then pan
if (m_mouse.is_start_position_2D_defined()) {
// get point in model space at Z = 0
@@ -4580,10 +4631,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
}
}
else if ((evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) ||
(m_camera_movement && !is_camera_rotate(evt, swap_mouse_buttons) && !is_camera_pan(evt, swap_mouse_buttons))) {
(m_camera_movement && !is_camera_rotate(evt, button_mappings) && !is_camera_pan(evt, button_mappings))) {
m_mouse.position = pos.cast<double>();
if (swap_mouse_buttons ? evt.RightUp() : evt.LeftUp()) {
// Check if the button that was released is mapped to rotation
if ((evt.LeftUp() && button_mappings[MouseButton::Left] == MouseAction::Rotation) ||
(evt.MiddleUp() && button_mappings[MouseButton::Middle] == MouseAction::Rotation) ||
(evt.RightUp() && button_mappings[MouseButton::Right] == MouseAction::Rotation)) {
m_rotation_center(0) = m_rotation_center(1) = m_rotation_center(2) = 0.f;
}
@@ -4784,21 +4838,42 @@ void GLCanvas3D::on_set_focus(wxFocusEvent& evt)
m_is_touchpad_navigation = wxGetApp().app_config->get_bool("camera_navigation_style");
}
bool GLCanvas3D::is_camera_rotate(const wxMouseEvent& evt, const bool buttonsSwapped) const
bool GLCanvas3D::clicked_button_matches_action(const wxMouseEvent& evt, const MouseAction action, const std::map<MouseButton, MouseAction>& mappings) const
{
MouseButton clicked = MouseButton::None;
if (evt.LeftIsDown()) {
clicked = MouseButton::Left;
}
if (evt.MiddleIsDown()) {
clicked = MouseButton::Middle;
}
if (evt.RightIsDown()) {
clicked = MouseButton::Right;
}
auto it = mappings.find(clicked);
if (it == mappings.end()) {
return false;
}
return it->second == action;
}
bool GLCanvas3D::is_camera_rotate(const wxMouseEvent& evt, const std::map<MouseButton, MouseAction>& mappings) const
{
if (m_is_touchpad_navigation) {
return evt.Moving() && evt.AltDown() && !evt.ShiftDown();
} else {
return evt.Dragging() && (buttonsSwapped ? evt.RightIsDown() : evt.LeftIsDown());
return evt.Dragging() && clicked_button_matches_action(evt, MouseAction::Rotation, mappings);
}
}
bool GLCanvas3D::is_camera_pan(const wxMouseEvent& evt, const bool buttonsSwapped) const
bool GLCanvas3D::is_camera_pan(const wxMouseEvent& evt, const std::map<MouseButton, MouseAction>& mappings) const
{
if (m_is_touchpad_navigation) {
return evt.Moving() && evt.ShiftDown() && !evt.AltDown();
} else {
return evt.Dragging() && (evt.MiddleIsDown() || (buttonsSwapped ? evt.LeftIsDown() : evt.RightIsDown()));
return evt.Dragging() && clicked_button_matches_action(evt, MouseAction::Pan, mappings);
;
}
}
@@ -7621,6 +7696,105 @@ void GLCanvas3D::_rectangular_selection_picking_pass()
_update_volumes_hover_state();
}
bool GLCanvas3D::_is_fxaa_enabled() const
{
return wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_FXAA_ENABLED);
}
int GLCanvas3D::_get_effective_fps_cap() const
{
if (wxGetApp().app_config == nullptr)
return 0;
int fps_cap = 0;
try {
fps_cap = std::stoi(wxGetApp().app_config->get(SETTING_OPENGL_FPS_CAP));
}
catch (...) {
fps_cap = 0;
}
fps_cap = std::max(0, std::min(fps_cap, 240));
return fps_cap;
}
bool GLCanvas3D::_is_fps_overlay_enabled() const
{
return wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY);
}
void GLCanvas3D::_render_fps_overlay(int fps) const
{
if (fps < 0)
return;
ImGuiWrapper& imgui = *wxGetApp().imgui();
const float margin = 10.0f * get_scale();
const ImVec2 display_size = ImGui::GetIO().DisplaySize;
ImGui::SetNextWindowPos(ImVec2(display_size.x - margin, margin), ImGuiCond_Always, ImVec2(1.0f, 0.0f));
ImGui::SetNextWindowBgAlpha(0.35f);
imgui.begin(
std::string("###fps_overlay"),
ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoInputs);
imgui.text(std::string("FPS: ") + std::to_string(fps));
imgui.end();
}
void GLCanvas3D::_render_fxaa_pass(unsigned int width, unsigned int height)
{
if (width == 0 || height == 0)
return;
GLShaderProgram* shader = wxGetApp().get_shader("fxaa");
if (shader == nullptr)
return;
if (m_fxaa_texture_id == 0) {
glsafe(::glGenTextures(1, &m_fxaa_texture_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_fxaa_texture_id));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
}
glsafe(::glBindTexture(GL_TEXTURE_2D, m_fxaa_texture_id));
if (m_fxaa_texture_size[0] != width || m_fxaa_texture_size[1] != height) {
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
m_fxaa_texture_size = { width, height };
}
glsafe(::glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, width, height));
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glDisable(GL_BLEND));
glsafe(::glClear(GL_COLOR_BUFFER_BIT));
shader->start_using();
shader->set_uniform("view_model_matrix", Transform3d::Identity());
shader->set_uniform("projection_matrix", Transform3d::Identity());
shader->set_uniform("uniform_texture", 0);
shader->set_uniform("inv_tex_size", Vec2f(1.0f / static_cast<float>(width), 1.0f / static_cast<float>(height)));
glsafe(::glActiveTexture(GL_TEXTURE0));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_fxaa_texture_id));
m_background.render();
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
shader->stop_using();
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
}
void GLCanvas3D::_render_background()
{
bool use_error_color = false;
+15 -4
View File
@@ -290,8 +290,8 @@ class GLCanvas3D
bool is_enabled() const { return m_enabled; }
void set_enabled(bool enabled) { m_enabled = is_allowed() && enabled; }
void render_variable_layer_height_dialog(const GLCanvas3D& canvas);
void render_overlay(const GLCanvas3D& canvas);
void render_variable_layer_height_dialog(GLCanvas3D& canvas);
void render_overlay(GLCanvas3D& canvas);
void render_volumes(const GLCanvas3D& canvas, const GLVolumeCollection& volumes);
void adjust_layer_height_profile();
@@ -610,6 +610,7 @@ private:
bool m_reload_delayed;
RenderStats m_render_stats;
std::chrono::time_point<std::chrono::steady_clock> m_last_frame_start_time{ std::chrono::steady_clock::now() };
int m_imgui_undo_redo_hovered_pos{ -1 };
int m_mouse_wheel{ 0 };
@@ -727,6 +728,8 @@ public:
CameraTarget m_camera_target;
#endif // ENABLE_SHOW_CAMERA_TARGET
GLModel m_background;
unsigned int m_fxaa_texture_id{ 0 };
std::array<unsigned int, 2> m_fxaa_texture_size{ 0, 0 };
public:
explicit GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed);
~GLCanvas3D();
@@ -1038,8 +1041,11 @@ public:
void on_set_focus(wxFocusEvent& evt);
void force_set_focus();
bool is_camera_rotate(const wxMouseEvent& evt, const bool buttonsSwapped) const;
bool is_camera_pan(const wxMouseEvent& evt, const bool buttonsSwapped) const;
enum class MouseButton { None, Left, Middle, Right };
enum class MouseAction { None, Pan, Rotation };
bool clicked_button_matches_action(const wxMouseEvent& evt, MouseAction action, const std::map<MouseButton, MouseAction>& mappings) const;
bool is_camera_rotate(const wxMouseEvent& evt, const std::map<MouseButton, MouseAction>& mappings) const;
bool is_camera_pan(const wxMouseEvent& evt, const std::map<MouseButton, MouseAction>& mappings) const;
Size get_canvas_size() const;
Vec2d get_local_mouse_position() const;
@@ -1238,6 +1244,11 @@ private:
void _rectangular_selection_picking_pass();
// IMEX ghost picking (ray vs. ghost bbox). Runs at the end of _picking_pass.
void _picking_pass_imex_ghosts();
bool _is_fxaa_enabled() const;
int _get_effective_fps_cap() const;
bool _is_fps_overlay_enabled() const;
void _render_fps_overlay(int fps) const;
void _render_fxaa_pass(unsigned int width, unsigned int height);
void _render_background();
void _render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_axes);
//BBS: add part plate related logic
+2
View File
@@ -48,6 +48,8 @@ std::pair<bool, std::string> GLShadersManager::init()
valid &= append_shader("flat_clip", { prefix + "flat_clip.vs", prefix + "flat_clip.fs" });
// basic shader for textures, used to render textures
valid &= append_shader("flat_texture", { prefix + "flat_texture.vs", prefix + "flat_texture.fs" });
// used to apply post-processing antialiasing in screen space
valid &= append_shader("fxaa", { prefix + "fxaa.vs", prefix + "fxaa.fs" });
// used to render 3D scene background
valid &= append_shader("background", { prefix + "background.vs", prefix + "background.fs" });
#if SLIC3R_OPENGL_ES
+3 -1
View File
@@ -663,7 +663,9 @@ void GLTexture::render_texture(unsigned int tex_id, float left, float right, flo
void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const GLTexture::Quad_UVs& uvs)
{
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
// Orca: fix washed-out toolbar icons on Wayland: keep destination alpha at 1.0 so the compositor
// does not treat anti-aliased icon edges as window transparency.
glsafe(::glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
glsafe(::glEnable(GL_TEXTURE_2D));
glsafe(::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE));
+167 -40
View File
@@ -11,6 +11,7 @@
#include "Downloader.hpp"
#include <boost/chrono/duration.hpp>
#include <boost/log/detail/native_typeof.hpp>
#include <libslic3r/Config.hpp>
#include <wx/event.h>
// Localization headers: include libslic3r version first so everything in this file
@@ -100,6 +101,9 @@
#include "Mouse3DController.hpp"
#include "RemovableDriveManager.hpp"
#include "InstanceCheck.hpp"
#ifdef __APPLE__
#include "DeepLinkHandlerMac.h"
#endif
#include "NotificationManager.hpp"
#include "UnsavedChangesDialog.hpp"
#include "SavePresetDialog.hpp"
@@ -783,9 +787,20 @@ void GUI_App::post_init()
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
plater_->select_view_3D("3D");
//BBS init the opengl resource here
//#ifdef __linux__
if (plater_->canvas3D()->get_wxglcanvas()->IsShownOnScreen()&&plater_->canvas3D()->make_current_for_postinit()) {
//#endif
if (!plater_->canvas3D()->get_wxglcanvas()->IsShownOnScreen() ||
!plater_->canvas3D()->make_current_for_postinit()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": glcontext not ready, postpone init";
plater_->canvas3D()->enable_render(true);
plater_->canvas3D()->set_as_dirty();
#ifdef __linux__
// Wayland/EGL may not have committed the GL surface yet; ask the
// idle loop to retry post_init when the canvas is actually mapped.
// Without this, GL function pointers stay null and the first
// Preview focus crashes in Camera::apply_viewport.
m_post_initialized = false;
return;
#endif
} else {
Size canvas_size = plater_->canvas3D()->get_canvas_size();
wxGetApp().imgui()->set_display_size(static_cast<float>(canvas_size.get_width()), static_cast<float>(canvas_size.get_height()));
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", start to init opengl";
@@ -805,14 +820,7 @@ void GUI_App::post_init()
plater_->canvas3D()->render(false);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished rendering a first frame for test";
}
//#ifdef __linux__
}
else {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "Found glcontext not ready, postpone the init";
plater_->canvas3D()->enable_render(true);
plater_->canvas3D()->set_as_dirty();
}
//#endif
if (is_editor())
mainframe->select_tab(size_t(0));
if (app_config->get("default_page") == "1")
@@ -1900,6 +1908,7 @@ void GUI_App::init_networking_callbacks()
}
if (return_code == 5) {
GUI::wxGetApp().CallAfter([this, provider = event.provider] {
BOOST_LOG_TRIVIAL(info) << "logout: login expired";
this->request_user_logout(provider);
MessageDialog msg_dlg(nullptr, _L("Login information expired. Please login again."), "", wxAPPLY | wxOK);
if (msg_dlg.ShowModal() == wxOK) {
@@ -2546,6 +2555,12 @@ std::string get_system_info()
bool GUI_App::on_init_inner()
{
wxLog::SetActiveTarget(new wxBoostLog());
#ifdef __APPLE__
// Override wxWidgets' kAEGetURL handler so orcaslicer:// deep links keep
// working after the wxWidgets 3.3.2 upgrade on macOS (#13119).
register_mac_deep_link_handler();
#endif
#if BBL_RELEASE_TO_PUBLIC
wxLog::SetLogLevel(wxLOG_Message);
#endif
@@ -4526,7 +4541,8 @@ std::string GUI_App::handle_web_request(std::string cmd)
}
else if (command_str.compare("homepage_logout") == 0) {
CallAfter([this] {
wxGetApp().request_user_logout();
BOOST_LOG_TRIVIAL(info) << "logout: homepage_logout";
request_user_logout();
});
}
else if (command_str.compare("get_orca_login_info") == 0) {
@@ -4539,18 +4555,22 @@ std::string GUI_App::handle_web_request(std::string cmd)
CallAfter([this] { request_login(true, BBL_CLOUD_PROVIDER); });
}
else if (command_str.compare("homepage_bambu_logout") == 0) {
CallAfter([this] { request_user_logout(BBL_CLOUD_PROVIDER); });
CallAfter([this] {
BOOST_LOG_TRIVIAL(info) << "logout: homepage_bambu_logout";
request_user_logout(BBL_CLOUD_PROVIDER);
});
}
else if (command_str.compare("homepage_orca_login_or_register") == 0) {
CallAfter([this] { request_login(true, ORCA_CLOUD_PROVIDER); });
}
else if (command_str.compare("homepage_orca_logout") == 0) {
CallAfter([this] { request_user_logout(ORCA_CLOUD_PROVIDER); });
CallAfter([this] {
BOOST_LOG_TRIVIAL(info) << "logout: homepage_orca_logout";
request_user_logout(ORCA_CLOUD_PROVIDER);
});
}
else if (command_str.compare("homepage_modeldepot") == 0) {
CallAfter([this] {
wxGetApp().open_mall_page_dialog();
});
CallAfter([this] { open_mall_page_dialog(); });
}
else if (command_str.compare("homepage_newproject") == 0) {
this->request_open_project("<new>");
@@ -4783,7 +4803,7 @@ void GUI_App::handle_http_error(unsigned int status, std::string body, const std
void GUI_App::on_http_error(wxCommandEvent &evt)
{
int status = evt.GetInt();
std::string provider = ORCA_CLOUD_PROVIDER;
std::string provider = "";
std::string body_str;
// Extract provider and body from event data
@@ -4825,22 +4845,63 @@ void GUI_App::on_http_error(wxCommandEvent &evt)
// request login
if (status == 401) {
if (m_agent) {
if (m_agent->is_user_login(provider)) {
this->request_user_logout(provider);
if (!provider.empty() && m_agent->is_user_login(provider)) {
if (std::chrono::steady_clock::now() - m_last_401_error_time > 30s) {
BOOST_LOG_TRIVIAL(warning) << "logout: http error 401.";
this->request_user_logout(provider);
if (!m_show_http_errpr_msgdlg) {
MessageDialog msg_dlg(nullptr, _L("Login information expired. Please login again."), "", wxAPPLY | wxOK);
m_show_http_errpr_msgdlg = true;
auto modal_result = msg_dlg.ShowModal();
if (modal_result == wxOK || modal_result == wxCLOSE) {
m_show_http_errpr_msgdlg = false;
return;
if (!m_show_http_error_msgdlg) {
MessageDialog msg_dlg(nullptr, _L("Login information expired. Please login again."), "", wxAPPLY | wxOK);
m_show_http_error_msgdlg = true;
auto modal_result = msg_dlg.ShowModal();
if (modal_result == wxOK || modal_result == wxCLOSE) {
m_show_http_error_msgdlg = false;
return;
}
}
m_last_401_error_time = std::chrono::steady_clock::now();
} else {
BOOST_LOG_TRIVIAL(warning) << "401 encountered within grace period, suppressing logout";
}
}
}
return;
}
// No need to show dialog for 410: 410 means resource has been deleted from the server.
if (status == 410) {
BOOST_LOG_TRIVIAL(info) << "Http error 410.";
return;
}
static bool m_is_error_shown = false;
// Show general error notification for Orca Cloud API failures (not Bambu)
if (provider == ORCA_CLOUD_PROVIDER && status >= 400 && code != HttpErrorVersionLimited) {
wxString msg;
if (!error.empty()) {
msg = wxString::Format(_L("Failed to connect to OrcaCloud.\nPlease check your network connectivity\n(HTTP %u): %s"), status, wxString::FromUTF8(error));
} else {
msg = wxString::Format(_L("Failed to connect to OrcaCloud.\nPlease check your network connectivity\n(HTTP %u)"), status);
}
if (app_config->get_bool("developer_mode")) {
// Use notification manager if ImGui is ready; fall back to wxMessageBox on Linux
// where ImGui may not be initialized until the user switches to the Prepare tab.
if (wxGetApp().plater() != nullptr && wxGetApp().imgui()->display_initialized()) {
wxGetApp()
.plater()
->get_notification_manager()
->push_notification(NotificationType::PlaterError, NotificationManager::NotificationLevel::WarningNotificationLevel,
msg.ToUTF8().data());
}
}
if (!m_is_error_shown) {
m_is_error_shown = true;
wxMessageBox(msg, _L("Cloud Error"), wxOK | wxICON_ERROR, wxGetApp().mainframe);
}
}
}
void GUI_App::enable_user_preset_folder(bool enable)
@@ -4876,6 +4937,10 @@ void GUI_App::on_user_login_handle(wxCommandEvent &evt)
std::string provider = evt.GetString().ToStdString();
if (provider.empty()) provider = ORCA_CLOUD_PROVIDER;
// Reset 401 grace period so transient token-propagation 401s
// during login warmup don't trigger immediate logout.
m_last_401_error_time = std::chrono::steady_clock::now();
m_agent->connect_server();
// get machine list
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
@@ -5599,6 +5664,7 @@ void GUI_App::show_check_privacy_dlg(wxCommandEvent& evt)
privacy_dlg.Bind(EVT_PRIVACY_UPDATE_CANCEL, [this, provider](wxCommandEvent &e) {
app_config->set_bool("privacy_update_checked", false);
if (m_agent) {
BOOST_LOG_TRIVIAL(info) << "logout: Privacy update dialog cancelled.";
m_agent->user_logout(false, provider);
post_logout_to_webview(provider);
}
@@ -5785,18 +5851,32 @@ void GUI_App::reload_settings()
return;
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " cloud user preset number is: " << user_presets.size();
// Check the user presets for any system vendors that need to be installed
for (auto data : user_presets) {
if (!check_preset_parent_available(data))
add_pending_vendor_preset(data);
}
load_pending_vendors();
preset_bundle->load_user_presets(*app_config, user_presets, ForwardCompatibilitySubstitutionRule::Enable);
preset_bundle->save_user_presets(*app_config, get_delete_cache_presets());
if (is_main_thread_active())
auto refresh_synced_ui = [this, user_presets = std::move(user_presets)]() mutable {
if (is_closing() || !preset_bundle || !app_config || !mainframe)
return;
// Check the user presets for any system vendors that need to be installed
for (auto data : user_presets) {
if (!check_preset_parent_available(data))
add_pending_vendor_preset(data);
}
load_pending_vendors();
preset_bundle->load_user_presets(*app_config, user_presets, ForwardCompatibilitySubstitutionRule::Enable);
preset_bundle->save_user_presets(*app_config, get_delete_cache_presets());
// Orca: settings changed, refresh ui to reflect the new preset values
mainframe->update_side_preset_ui();
for (auto tab : tabs_list) {
tab->reload_config();
tab->update_changed_ui();
}
if (plater_)
plater_->sidebar().update_all_preset_comboboxes();
};
if (is_main_thread_active())
refresh_synced_ui();
else
CallAfter([this] { mainframe->update_side_preset_ui(); });
CallAfter(refresh_synced_ui);
}
}
@@ -6521,8 +6601,8 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
dlg->Update(percent, _L("Loading user preset"));
});
};
cancelFn = [this, dlg]() {
return is_closing() || dlg->WasCanceled();
cancelFn = [this, dlg, t = std::weak_ptr<int>(m_user_sync_token)]() {
return is_closing() || dlg->WasCanceled() || t.expired();
};
finishFn = [this, dlg](bool) {
CallAfter([=]{ dlg->Destroy(); });
@@ -6530,8 +6610,8 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
}
else {
finishFn = [](bool) {}; // reload_settings() is now triggered from the background thread
cancelFn = [this]() {
return is_closing();
cancelFn = [this, t = std::weak_ptr<int>(m_user_sync_token)]() {
return is_closing() || t.expired();
};
}
@@ -6763,9 +6843,41 @@ void GUI_App::stop_sync_user_preset()
}
}
void GUI_App::restart_sync_user_preset()
{
if (!m_user_sync_token) {
// No sync running. If a restart helper is already in flight it will
// start the new sync once the old thread is joined — don't race it.
if (!m_restart_sync_pending)
start_sync_user_preset(true);
return;
}
// Resetting the token signals the old thread to stop (cancelFn checks
// t.expired(), so it exits after its current HTTP request completes).
// A helper thread joins the old thread off the UI thread — no freeze —
// then starts the new sync via CallAfter once the old one is fully done.
m_user_sync_token.reset();
m_restart_sync_pending = true;
auto old_thread = std::move(m_sync_update_thread);
std::thread([this, old_thread = std::move(old_thread)]() mutable {
if (old_thread.joinable())
old_thread.join();
m_restart_sync_pending = false;
if (!is_closing())
CallAfter([this]() {
if (!is_closing())
start_sync_user_preset(true);
});
}).detach();
}
void GUI_App::on_stealth_mode_enter()
{
stop_sync_user_preset();
BOOST_LOG_TRIVIAL(info) << "logout: on_stealth_mode_enter";
request_user_logout(ORCA_CLOUD_PROVIDER);
request_user_logout(BBL_CLOUD_PROVIDER);
if (mainframe && mainframe->m_webview) {
@@ -7553,6 +7665,13 @@ void GUI_App::open_exportpresetbundledialog(size_t open_on_tab, const std::strin
void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_option)
{
static constexpr const char* opengl_fxaa_setting_key = "opengl_fxaa_enabled";
static constexpr const char* opengl_fps_cap_setting_key = "opengl_fps_cap";
static constexpr const char* opengl_show_fps_overlay_setting_key = "opengl_show_fps_overlay";
const std::string previous_opengl_fxaa = app_config->get(opengl_fxaa_setting_key);
const std::string previous_opengl_fps_cap = app_config->get(opengl_fps_cap_setting_key);
const std::string previous_opengl_show_fps_overlay = app_config->get(opengl_show_fps_overlay_setting_key);
bool need_recreate_gui = false;
std::string pending_language;
{
@@ -7591,6 +7710,14 @@ void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_
}
}
const bool opengl_fxaa_changed = app_config->get(opengl_fxaa_setting_key) != previous_opengl_fxaa;
const bool opengl_fps_cap_changed = app_config->get(opengl_fps_cap_setting_key) != previous_opengl_fps_cap;
const bool opengl_show_fps_overlay_changed = app_config->get(opengl_show_fps_overlay_setting_key) != previous_opengl_show_fps_overlay;
if ((opengl_fxaa_changed || opengl_fps_cap_changed || opengl_show_fps_overlay_changed) && !need_recreate_gui && this->plater_ != nullptr) {
this->plater_->set_current_canvas_as_dirty();
this->plater_->get_current_canvas3D()->force_set_focus();
}
if (!pending_language.empty()) {
const std::string previous_language = app_config->get("language");
app_config->set("language", pending_language);
+5 -2
View File
@@ -321,10 +321,12 @@ private:
boost::thread m_sync_update_thread;
std::shared_ptr<int> m_user_sync_token;
std::atomic<bool> m_restart_sync_pending {false};
bool m_is_dark_mode{ false };
bool m_adding_script_handler { false };
bool m_side_popup_status{false};
bool m_show_http_errpr_msgdlg{false};
bool m_show_http_error_msgdlg{false};
std::chrono::steady_clock::time_point m_last_401_error_time;
bool m_show_error_msgdlg{false};
wxString m_info_dialog_content;
HttpServer m_http_server;
@@ -492,7 +494,7 @@ public:
void request_open_project(std::string project_id);
void request_remove_project(std::string project_id);
void handle_http_error(unsigned int status, std::string body, const std::string& provider = ORCA_CLOUD_PROVIDER);
void handle_http_error(unsigned int status, std::string body, const std::string& provider = "");
void on_http_error(wxCommandEvent &evt);
void on_update_machine_list(wxCommandEvent& evt);
void on_user_login(wxCommandEvent &evt);
@@ -530,6 +532,7 @@ public:
void sync_preset(Preset* preset);
void start_sync_user_preset(bool with_progress_dlg = false);
void stop_sync_user_preset();
void restart_sync_user_preset();
void on_stealth_mode_enter();
// Bundle subscription sync
+14 -11
View File
@@ -1644,17 +1644,6 @@ void MenuFactory::create_filament_action_menu(bool init, int active_filament_men
[]() { return true; }, m_parent);
}
if (init) {
append_menu_item(
menu, wxID_ANY, _L("Delete"), _L("Delete this filament"), [](wxCommandEvent&) {
plater()->sidebar().delete_filament(-2); }, "", nullptr,
[]() {
return plater()->sidebar().combos_filament().size() > 1
// Orca: only show delete filament option for SEMM machines unless is BBL
&& Sidebar::should_show_SEMM_buttons();
}, m_parent);
}
const int item_id = menu->FindItem(_L("Merge with"));
if (item_id != wxNOT_FOUND)
menu->Destroy(item_id);
@@ -1675,6 +1664,20 @@ void MenuFactory::create_filament_action_menu(bool init, int active_filament_men
}
append_submenu(menu, sub_menu, wxID_ANY, _L("Merge with"), "", "",
[filaments_cnt]() { return filaments_cnt > 1; }, m_parent);
// ORCA use delete item on end of menu to prevent accidental clicks. clicking to submenus(merge) already not allowed by OS
const int delete_id = menu->FindItem(_L("Delete"));
if (delete_id != wxNOT_FOUND)
menu->Destroy(delete_id);
append_menu_item(
menu, wxID_ANY, _L("Delete"), _L("Delete this filament"), [](wxCommandEvent&) {
plater()->sidebar().delete_filament(-2); }, "", nullptr,
[]() {
return plater()->sidebar().combos_filament().size() > 1
// Orca: only show delete filament option for SEMM machines unless is BBL
&& Sidebar::should_show_SEMM_buttons();
}, m_parent);
}
//BBS: add part plate related logic
+151 -18
View File
@@ -435,15 +435,25 @@ void ObjectList::create_objects_ctrl()
// column Extruder of the view control:
BitmapChoiceRenderer* bmp_choice_renderer = new BitmapChoiceRenderer();
bmp_choice_renderer->set_can_create_editor_ctrl_function([this]() {
return m_objects_model->GetItemType(GetSelection()) & (itVolume | itLayer | itObject);
const auto get_filament_context_item = [this]() {
#ifdef __WXOSX__
return m_filament_editor_item.IsOk() ? m_filament_editor_item : GetSelection();
#else
return GetSelection();
#endif
};
bmp_choice_renderer->set_can_create_editor_ctrl_function([this, get_filament_context_item]() {
const wxDataViewItem item = get_filament_context_item();
return m_objects_model->GetItemType(item) & (itVolume | itLayer | itObject);
});
bmp_choice_renderer->set_default_extruder_idx([this]() {
return m_objects_model->GetDefaultExtruderIdx(GetSelection());
bmp_choice_renderer->set_default_extruder_idx([this, get_filament_context_item]() {
const wxDataViewItem item = get_filament_context_item();
return m_objects_model->GetDefaultExtruderIdx(item);
});
bmp_choice_renderer->set_has_default_extruder([this]() {
return m_objects_model->GetVolumeType(GetSelection()) == ModelVolumeType::PARAMETER_MODIFIER ||
m_objects_model->GetItemType(GetSelection()) == itLayer;
bmp_choice_renderer->set_has_default_extruder([this, get_filament_context_item]() {
const wxDataViewItem item = get_filament_context_item();
return m_objects_model->GetVolumeType(item) == ModelVolumeType::PARAMETER_MODIFIER ||
m_objects_model->GetItemType(item) == itLayer;
});
AppendColumn(new wxDataViewColumn(_L("Fila."), bmp_choice_renderer,
colFilament, m_columns_width[colFilament] * em, wxALIGN_CENTER_HORIZONTAL, 0));
@@ -461,11 +471,16 @@ void ObjectList::create_objects_ctrl()
wxALIGN_CENTER_HORIZONTAL, 0);
// Open filament editor faster
// On macOS, bypass wxDataViewCtrl::EditItem() for this custom renderer to avoid
// native text editing of wxCustomRendererObject.
this->Bind(wxEVT_DATAVIEW_ITEM_ACTIVATED, [this](wxDataViewEvent& event) {
if (event.GetColumn() == colFilament) {
#ifdef __WXOSX__
start_filament_editor(event.GetItem());
#else
// Trigger the editor opening manually
this->EditItem(event.GetItem(), GetColumn(colFilament));
#endif
}
});
@@ -1082,10 +1097,11 @@ void ObjectList::update_filament_in_config(const wxDataViewItem& item)
if (m_prevent_update_filament_in_config)
return;
ModelConfig* config = nullptr;
const ItemType item_type = m_objects_model->GetItemType(item);
if (item_type & itObject) {
const int obj_idx = m_objects_model->GetIdByItem(item);
m_config = &(*m_objects)[obj_idx]->config;
config = &(*m_objects)[obj_idx]->config;
}
else {
const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetObject(item));
@@ -1094,15 +1110,17 @@ void ObjectList::update_filament_in_config(const wxDataViewItem& item)
if (obj_idx < 0 || ui_volume_idx < 0)
return;
int volume_in3d_idx = m_objects_model->get_real_volume_index_in_3d(obj_idx,ui_volume_idx);
m_config = &(*m_objects)[obj_idx]->volumes[volume_in3d_idx]->config;
config = &(*m_objects)[obj_idx]->volumes[volume_in3d_idx]->config;
}
else if (item_type & itLayer)
m_config = &get_item_config(item);
config = &get_item_config(item);
}
if (!m_config)
if (!config)
return;
m_config = config;
take_snapshot("Change Filament");
const int extruder = m_objects_model->GetExtruderNumber(item);
@@ -2849,7 +2867,7 @@ void ObjectList::split()
take_snapshot("Split to parts");
volume->split(filament_cnt);
volume->split(filament_cnt, wxGetApp().app_config->get_bool("keep_painting"));
wxBusyCursor wait;
@@ -3041,7 +3059,6 @@ void ObjectList::merge(bool to_multipart_object)
auto opt = object->config.option("extruder");
if (opt) { new_volume->config.set_key_value("extruder", new ConfigOptionInt(opt->getInt())); }
}
new_volume->mmu_segmentation_facets.assign(std::move(volume->mmu_segmentation_facets));
}
new_object->sort_volumes(true);
@@ -3226,6 +3243,22 @@ void ObjectList::boolean()
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "boolean");
ModelObject* object = (*m_objects)[obj_idxs.front()];
const bool keep_painting = wxGetApp().app_config->get_bool("keep_painting");
std::vector<std::optional<TriangleSelector::SavedPainting>> saved_paintings;
if (keep_painting) {
// Save painting of all the positive parts
saved_paintings.reserve(object->volumes.size());
for (const ModelVolume* vol : object->volumes) {
if (vol && vol->mesh_ptr() && vol->is_model_part() && vol->is_any_painted()) {
saved_paintings.emplace_back(vol->save_painting());
if (saved_paintings.back()) {
saved_paintings.back()->mesh.transform(vol->get_matrix(), true);
}
}
}
}
TriangleMesh mesh = Plater::combine_mesh_fff(*object, -1, [this](const std::string& msg) {return wxGetApp().notification_manager()->push_plater_error_notification(msg); });
// add mesh to model as a new object, keep the original object's name and config
@@ -3237,6 +3270,29 @@ void ObjectList::boolean()
new_object->add_instance();
ModelVolume* new_volume = new_object->add_volume(mesh);
// Remap paint
if (keep_painting) {
for (auto& saved_painting : saved_paintings) {
if (saved_painting) {
// For each original painted volume, we need to apply to each instance
// because we merged all instances into one in `combine_mesh_fff`
// First we save the non-instance-translated mesh
TriangleMesh vols_mesh(std::move(saved_painting->mesh));
for (const ModelInstance* i : object->instances) {
// Then for each instance, we apply the paint at the given instance place
saved_painting->mesh = vols_mesh;
saved_painting->mesh.transform(i->get_matrix());
// Then paint it
new_volume->restore_painting(saved_painting, true);
}
}
}
}
// BBS: ensure on bed but no need to ensure locate in the center around origin
new_object->ensure_on_bed();
new_object->center_around_origin();
@@ -6048,10 +6104,13 @@ void ObjectList::fix_through_cgal()
msg += "\n";
}
plater->clear_before_change_mesh(obj_idx);
const bool keep_painting = GUI::wxGetApp().app_config->get_bool("keep_painting");
if (!keep_painting) {
plater->clear_before_change_mesh(obj_idx);
}
const size_t volumes_before = object(obj_idx)->volumes.size();
std::string res;
if (!fix_model_with_cgal_gui(*(object(obj_idx)), vol_idx, progress_dlg, msg, res))
if (!fix_model_with_cgal_gui(*(object(obj_idx)), vol_idx, progress_dlg, msg, res, keep_painting))
return false;
//wxGetApp().plater()->changed_mesh(obj_idx);
object(obj_idx)->ensure_on_bed();
@@ -6169,6 +6228,7 @@ void GUI::ObjectList::smooth_mesh()
WarningDialog dlg(static_cast<wxWindow *>(wxGetApp().mainframe), content, wxEmptyString, wxOK);
dlg.ShowModal();
};
const bool keep_painting = GUI::wxGetApp().app_config->get_bool("keep_painting");
bool has_show_smooth_mesh_error_dlg = false;
if (vol_idxs.empty()) {
obj = object(object_idx);
@@ -6180,8 +6240,11 @@ void GUI::ObjectList::smooth_mesh()
bool ok;
auto result_mesh = TriangleMeshDeal::smooth_triangle_mesh(mv->mesh(), ok);
if (ok) {
const std::optional<TriangleSelector::SavedPainting> saved_painting = keep_painting ?
mv->save_painting() :
std::optional<TriangleSelector::SavedPainting>{};
mv->set_mesh(result_mesh);
mv->reset_extra_facets(); // reset paint color
mv->restore_painting(saved_painting);
mv->calculate_convex_hull();
mv->invalidate_convex_hull_2d();
mv->set_new_unique_id();
@@ -6206,8 +6269,11 @@ void GUI::ObjectList::smooth_mesh()
bool ok;
auto result_mesh = TriangleMeshDeal::smooth_triangle_mesh(mv->mesh(),ok);
if (ok) {
const std::optional<TriangleSelector::SavedPainting> saved_painting = keep_painting ?
mv->save_painting() :
std::optional<TriangleSelector::SavedPainting>{};
mv->set_mesh(result_mesh);
mv->reset_extra_facets(); // reset paint color
mv->restore_painting(saved_painting);
mv->calculate_convex_hull();
mv->invalidate_convex_hull_2d();
mv->set_new_unique_id();
@@ -6279,10 +6345,71 @@ void ObjectList::ItemValueChanged(wxDataViewEvent &event)
}
}
#ifdef __WXOSX__
bool ObjectList::is_live_model_item(wxDataViewItem item) const
{
if (!item.IsOk() || !m_objects_model)
return false;
wxDataViewItemArray all_items;
m_objects_model->GetAllChildren(wxDataViewItem(nullptr), all_items);
for (const wxDataViewItem& live_item : all_items)
if (live_item.GetID() == item.GetID())
return true;
return false;
}
void ObjectList::start_filament_editor(wxDataViewItem item)
{
if (m_starting_filament_editor || !is_live_model_item(item))
return;
const ItemType type = m_objects_model->GetItemType(item);
if (!(type & (itVolume | itLayer | itObject)))
return;
auto* column = GetColumn(colFilament);
if (!column)
return;
auto* custom_renderer = dynamic_cast<wxDataViewCustomRenderer*>(column->GetRenderer());
if (!custom_renderer || custom_renderer->GetEditorCtrl())
return;
m_starting_filament_editor = true;
m_filament_editor_item = item;
const bool started = custom_renderer->StartEditing(item, GetItemRect(item, column));
m_filament_editor_item = wxDataViewItem(nullptr);
m_starting_filament_editor = false;
if (!started)
return;
SetCustomRendererPtr(custom_renderer);
SetCustomRendererItem(item);
}
#endif // __WXOSX__
void GUI::ObjectList::OnStartEditing(wxDataViewEvent &event)
{
auto col = event.GetColumn();
auto item = event.GetItem();
#ifdef __WXOSX__
if (col == colFilament) {
if (m_starting_filament_editor)
return;
event.Veto();
CallAfter([this, item] {
if (!is_live_model_item(item))
return;
start_filament_editor(item);
});
return;
}
#endif // __WXOSX__
if (col == colName) {
ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)item.GetID();
if (node->GetType() & itPlate) {
@@ -6350,6 +6477,12 @@ void ObjectList::OnEditingStarted(wxDataViewEvent &event)
dynamic_cast<TabPrintModel*>(wxGetApp().get_model_tab(vol_idx >= 0))->reset_model_config();
return;
}
#ifdef __WXOSX__
if (col == colFilament) {
start_filament_editor(item);
return;
}
#endif // __WXOSX__
if (col != colFilament && col != colName)
return;
auto column = GetColumn(col);
+7
View File
@@ -192,6 +192,13 @@ private:
int m_last_selected_column = -1;
#endif /* __MSW__ */
#ifdef __WXOSX__
bool m_starting_filament_editor = false;
wxDataViewItem m_filament_editor_item { nullptr };
bool is_live_model_item(wxDataViewItem item) const;
void start_filament_editor(wxDataViewItem item);
#endif // __WXOSX__
#if 0
SettingsFactory::Bundle m_freq_settings_fff;
SettingsFactory::Bundle m_freq_settings_sla;
+16 -7
View File
@@ -561,14 +561,23 @@ wxString GridCellSupportEditor::ms_stringValues[2] = { wxT(""), wxT("") };
void GridCellSupportEditor::DoActivate(int row, int col, wxGrid* grid)
{
ObjectGrid* local_table = dynamic_cast<ObjectGrid*>(grid);
wxGridBlocks cell_array = grid->GetSelectedBlocks();
auto left_col = cell_array.begin()->GetLeftCol();
auto right_col = cell_array.begin()->GetRightCol();
auto top_row = cell_array.begin()->GetTopRow();
auto bottom_row = cell_array.begin()->GetBottomRow();
auto iter = cell_array.begin();
int left_col, right_col, top_row, bottom_row;
if (iter == cell_array.end()) {
// wxWidgets 3.3.x returns an empty range when nothing is selected;
// fall back to the cell that triggered activation so the single-cell
// branch below handles it.
left_col = right_col = col;
top_row = bottom_row = row;
} else {
left_col = iter->GetLeftCol();
right_col = iter->GetRightCol();
top_row = iter->GetTopRow();
bottom_row = iter->GetBottomRow();
}
if ((left_col == right_col) &&
(top_row == bottom_row)) {
wxGridCellBoolEditor::DoActivate(row, col, grid);
+36 -13
View File
@@ -104,20 +104,24 @@ void GLGizmoAssembly::on_render_input_window(float x, float y, float bottom_limi
}
show_selection_ui();
show_face_face_assembly_common();
ImGui::Separator();
show_face_face_assembly_senior();
show_distance_xyz_ui();
render_input_window_warning(m_same_model_object);
ImGui::Separator();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f));
GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y);
float f_scale =m_parent.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
ImGui::PopStyleVar(2);
GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y);
ImGui::SameLine();
GLGizmoUtils::begin_right_aligned_buttons({ _L("Done") });
if (m_imgui->button(_L("Done"))) {
m_parent.reset_all_gizmos();
}
render_input_window_warning(m_same_model_object);
if (last_feature != m_curr_feature || last_mode != m_mode || last_selected_features != m_selected_features) {
// the dialog may have changed its size, ask for an extra frame to render it properly
@@ -127,21 +131,40 @@ void GLGizmoAssembly::on_render_input_window(float x, float y, float bottom_limi
m_imgui->set_requires_extra_frame();
}
m_last_active_item_imgui = m_current_active_imgui_id;
GizmoImguiEnd();
// Orca
ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding
ImGuiWrapper::pop_toolbar_style();
}
void GLGizmoAssembly::render_input_window_warning(bool same_model_object)
{
if (wxGetApp().plater()->canvas3D()->get_canvas_type() == GLCanvas3D::ECanvasType::CanvasView3D) {
if (m_hit_different_volumes.size() == 2) {
if (same_model_object == false) {
m_imgui->warning_text(_L("Warning") + ": " +
_L("It is recommended to assemble the objects first,\nbecause the objects is restriced to bed \nand only parts can be lifted."));
}
const bool same_mesh_warning = m_hit_different_volumes.size() == 1;
const bool wrong_feature_warning = m_selected_wrong_feature_waring_tip;
const bool not_assembled_warning = m_hit_different_volumes.size() == 2 && same_model_object == false &&
wxGetApp().plater()->canvas3D()->get_canvas_type() == GLCanvas3D::ECanvasType::CanvasView3D;
if (same_mesh_warning || not_assembled_warning || wrong_feature_warning) {
ImGui::Separator();
}
if (same_mesh_warning) {
m_imgui->warning_text(_L("Warning: please select two different meshes."));
}
if (wrong_feature_warning) {
if (m_assembly_mode == AssemblyMode::FACE_FACE) {
m_imgui->warning_text(_L("Warning: please select Plane's feature."));
} else if (m_assembly_mode == AssemblyMode::POINT_POINT) {
m_imgui->warning_text(_L("Warning: please select Point's or Circle's feature."));
}
}
if (not_assembled_warning) {
m_imgui->warning_text(
_L("Warning") + ": " +
_L("It is recommended to assemble the objects first,\nbecause the objects is restriced to bed \nand only parts can be lifted.")
);
}
}
bool GLGizmoAssembly::render_assembly_mode_combo(double label_width, float item_width)
+106 -70
View File
@@ -48,19 +48,22 @@ bool GLGizmoBrimEars::on_init()
const wxString ctrl = GUI::shortkey_ctrl_prefix();
const wxString alt = GUI::shortkey_alt_prefix();
m_desc["head_diameter"] = _L("Head diameter");
m_desc["max_angle"] = _L("Max angle");
m_desc["detection_radius"] = _L("Detection radius");
m_desc["remove_selected"] = _L("Remove selected points");
m_desc["remove_all"] = _L("Remove all");
m_desc["auto_generate"] = _L("Auto-generate points");
m_desc["section_view"] = _L("Section view");
m_desc["head_diameter"] = _L("Head diameter");
m_desc["max_angle"] = _L("Max angle");
m_desc["detection_radius"] = _L("Detection radius");
m_desc["remove"] = _L("Remove");
m_desc["remove_selected"] = _L("Selected");
m_desc["remove_all"] = _L("All");
m_desc["create"] = _L("Create");
m_desc["auto_generate"] = _L("Auto-generate");
m_desc["auto-generate-tooltip"] = _L("Generate brim ears using Max angle and Detection radius");
m_desc["section_view"] = _L("Section view");
m_shortcuts = {
{_L("Left mouse button"), _L("Add a brim ear")},
{_L("Right mouse button"), _L("Delete a brim ear")},
{ctrl + _L("Mouse wheel"), _L("Adjust head diameter")},
{alt + _L("Mouse wheel"), _L("Adjust section view")},
{_L("Left mouse button"), _L("Add or Select")},
{_L("Right mouse button"), _L("Remove")},
{ctrl + _L("Mouse wheel"), m_desc["head_diameter"]},
{alt + _L("Mouse wheel"), m_desc["section_view"]},
};
return true;
@@ -545,6 +548,15 @@ void GLGizmoBrimEars::delete_selected_points()
update_model_object();
}
bool GLGizmoBrimEars::has_selected_points() const
{
for (const auto& entry : m_editing_cache) {
if (entry.selected)
return true;
}
return false;
}
void GLGizmoBrimEars::on_dragging(const UpdateData& data)
{
if (m_hover_id != -1) {
@@ -634,21 +646,21 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
y = std::min(y, bottom_limit - win_h);
GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f);
const float currt_scale = m_parent.get_scale();
ImGuiWrapper::push_toolbar_style(currt_scale);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0 * currt_scale, 5.0 * currt_scale));
ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize, 4.0f * currt_scale);
const float f_scale = m_parent.get_scale();
ImGuiWrapper::push_toolbar_style(f_scale);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
GizmoImguiBegin(get_name(),
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
float space_size = m_imgui->get_style_scaling() * 8;
std::vector<wxString> text_list = {m_desc["head_diameter"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["clipping_of_view"]};
std::vector<wxString> text_list = {m_desc["head_diameter"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["clipping_of_view"],
m_desc["create"], m_desc["remove"]};
float widest_text = m_imgui->find_widest_text(text_list);
float caption_size = widest_text + space_size + ImGui::GetStyle().WindowPadding.x;
float input_text_size = m_imgui->scaled(10.0f);
float button_size = ImGui::GetFrameHeight();
float list_width = input_text_size + ImGui::GetStyle().ScrollbarSize + 2 * currt_scale;
float list_width = input_text_size + ImGui::GetStyle().ScrollbarSize + 2 * f_scale;
const float slider_icon_width = m_imgui->get_slider_icon_size().x;
const float slider_width = list_width - space_size;
@@ -662,12 +674,12 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
if (last_y != y) last_y = y;
}
ImGui::AlignTextToFramePadding();
// Following is a nasty way to:
// - save the initial value of the slider before one starts messing with it
// - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene
// - take correct undo/redo snapshot after the user is done with moving the slider
ImGui::AlignTextToFramePadding();
float initial_value = m_new_point_head_diameter;
m_imgui->text(m_desc["head_diameter"]);
ImGui::SameLine(caption_size);
@@ -684,8 +696,10 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
ImGui::SameLine(drag_left_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##head_diameter_input", &m_new_point_head_diameter, 0.05f, 0.0f, 0.0f, "%.1f");
ImGui::AlignTextToFramePadding();
ImGui::Separator();
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc["max_angle"]);
ImGui::SameLine(caption_size);
ImGui::PushItemWidth(slider_width);
@@ -693,8 +707,8 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
ImGui::SameLine(drag_left_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##max_angle_input", &m_max_angle, 0.05f, 0.0f, 180.0f, "%.1f");
ImGui::AlignTextToFramePadding();
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc["detection_radius"]);
ImGui::SameLine(caption_size);
ImGui::PushItemWidth(slider_width);
@@ -702,8 +716,37 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
ImGui::SameLine(drag_left_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##detection_radius_input", &m_detection_radius, 0.05f, 0.0f, static_cast<float>(m_detection_radius_max), "%.1f");
ImGui::Separator();
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc["create"]);
ImGui::SameLine(caption_size);
if (m_imgui->button(m_desc["auto_generate"], m_desc["auto-generate-tooltip"])) {
auto_generate();
}
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc["remove"]);
ImGui::SameLine(caption_size);
m_imgui->disabled_begin(has_selected_points() == false);
if (m_imgui->button(m_desc["remove_selected"])) {
delete_selected_points();
}
m_imgui->disabled_end();
ImGui::SameLine();
m_imgui->disabled_begin(m_editing_cache.empty());
if (m_imgui->button(m_desc["remove_all"])) {
if (m_editing_cache.size() > 0) {
select_point(AllPoints);
delete_selected_points();
}
}
m_imgui->disabled_end();
ImGui::Separator();
ImGui::AlignTextToFramePadding();
float clp_dist = float(m_c->object_clipper()->get_position());
m_imgui->text(m_desc["section_view"]);
ImGui::SameLine(caption_size);
@@ -712,79 +755,72 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
ImGui::SameLine(drag_left_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
bool b_clp_dist_input = ImGui::BBLDragFloat("##section_view_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f");
if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position_by_ratio(clp_dist, false, true); }
ImGui::Separator();
// ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f));
float f_scale = m_parent.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
if (m_imgui->button(m_desc["auto_generate"])) { auto_generate(); }
if (m_imgui->button(m_desc["remove_selected"])) { delete_selected_points(); }
float font_size = ImGui::GetFontSize();
//ImGui::Dummy(ImVec2(font_size * 1, font_size * 1.3));
ImGui::SameLine();
if (m_imgui->button(m_desc["remove_all"])) {
if (m_editing_cache.size() > 0) {
select_point(AllPoints);
delete_selected_points();
}
if (slider_clp_dist || b_clp_dist_input) {
m_c->object_clipper()->set_position_by_ratio(clp_dist, false, true);
}
ImGui::PopStyleVar(1);
float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y;
ImGui::Separator();
GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y);
if (glb_cfg.opt_enum<BrimType>("brim_type") != btPainted) {
ImGui::SameLine();
auto link_text = [&]() {
ImColor HyperColor = ImGuiWrapper::COL_ORCA;
ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::to_ImVec4(ColorRGB::WARNING()));
float parent_width = ImGui::GetContentRegionAvail().x;
ImGui::SameLine();
GLGizmoUtils::begin_right_aligned_buttons({_L("Done")});
if (m_imgui->button(_L("Done"))) {
m_parent.reset_all_gizmos();
}
bool brim_not_painted = (obj_cfg.option("brim_type")) ? (obj_cfg.opt_enum<BrimType>("brim_type") != btPainted) :
(glb_cfg.opt_enum<BrimType>("brim_type") != btPainted);
bool has_invalid_ears = !m_single_brim.empty();
if (brim_not_painted || has_invalid_ears) {
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_WARNING);
float parent_width = ImGui::GetContentRegionAvail().x;
float font_size = ImGui::GetFontSize();
if (brim_not_painted) {
m_imgui->text_wrapped(_L("Warning: The brim type is not set to \"painted\", the brim ears will not take effect!"), parent_width);
ImGui::PopStyleColor();
ImColor HyperColor = ImGuiWrapper::COL_ORCA;
ImGui::PushStyleColor(ImGuiCol_Text, HyperColor.Value);
ImGui::Dummy(ImVec2(font_size * 1.8, font_size * 1.3));
ImGui::Dummy(ImVec2(font_size * 1.8f, 0.0f)); // Horizontal indent
ImGui::SameLine();
m_imgui->bold_text(_u8L("Set the brim type of this object to \"painted\""));
ImGui::PopStyleColor();
// underline
ImVec2 lineEnd = ImGui::GetItemRectMax();
lineEnd.y -= 2.0f;
ImVec2 lineStart = lineEnd;
lineStart.x = ImGui::GetItemRectMin().x;
ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, HyperColor);
if (ImGui::IsMouseHoveringRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), true)) {
ImVec2 min = ImGui::GetItemRectMin();
ImVec2 max = ImGui::GetItemRectMax();
ImGui::GetWindowDrawList()->AddLine(ImVec2(min.x, max.y - 2.0f), ImVec2(max.x, max.y - 2.0f), HyperColor);
if (ImGui::IsMouseHoveringRect(min, max)) {
m_link_text_hover = true;
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
DynamicPrintConfig new_conf = obj_cfg;
new_conf.set_key_value("brim_type", new ConfigOptionEnum<BrimType>(btPainted));
mo->config.assign_config(new_conf);
}
}else {
} else {
m_link_text_hover = false;
}
};
if (obj_cfg.option("brim_type")) {
if (obj_cfg.opt_enum<BrimType>("brim_type") != btPainted) {
link_text();
}
}else {
link_text();
ImGui::PopStyleColor(1); // Pop HyperColor
}
}
if (has_invalid_ears) {
wxString out = _L("Warning") + ": " + std::to_string(m_single_brim.size()) + " " + _L("invalid brim ears");
m_imgui->text_wrapped(out, parent_width);
}
if (!m_single_brim.empty()) {
wxString out = _L("Warning") + ": " + std::to_string(m_single_brim.size()) + _L(" invalid brim ears");
m_imgui->warning_text(out);
ImGui::PopStyleColor(1); // Pop Warning Color
} else {
// Reset hover state if no warnings are active
m_link_text_hover = false;
}
GizmoImguiEnd();
ImGui::PopStyleVar(2);
ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding
ImGuiWrapper::pop_toolbar_style();
}
@@ -80,6 +80,7 @@ public:
bool on_mouse(const wxMouseEvent& mouse_event) override;
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
void delete_selected_points();
bool has_selected_points() const;
void update_model_object();
//ClippingPlane get_sla_clipping_plane() const;
+486 -251
View File
@@ -227,6 +227,10 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename,
{"Groove Angle" , _u8L("Groove Angle")},
{"Cut position" , _u8L("Cut position")}, // ORCA
{"Build Volume" , _u8L("Build Volume")}, // ORCA
{"Multiple" , _u8L("Multiple")}, // ORCA
{"Count" , _u8L("Count")}, // ORCA
{"Gap" , _u8L("Gap")}, // ORCA
{"Spacing" , _u8L("Spacing")} // ORCA
};
// update_connector_shape();
@@ -543,7 +547,7 @@ bool GLGizmoCut3D::render_double_input(const std::string& label, double& value_i
return !is_approx(old_val, value);
}
bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float min_val/* = -0.1f*/, float max_tolerance/* = -0.1f*/)
bool GLGizmoCut3D::render_slider_two_input(const std::string& label, float& value_in, float& tolerance_in, float min_val/* = -0.1f*/, float max_tolerance/* = -0.1f*/)
{
// -------- [ ] -------- [ ]
// slider_with + item_in_gap + first_input_width + item_out_gap + slider_with + item_in_gap + second_input_width
@@ -613,6 +617,55 @@ bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& v
return !is_approx(old_val, value) || !is_approx(old_tolerance, tolerance);
}
bool GLGizmoCut3D::render_slider_input(const std::string& label, float& value_in, float min_val /* = -0.1f*/, float max_val /* = 100.f*/)
{
// -------- [ ]
// slider_with + input_width + slider_with + item_in_gap
double slider_with = 0.42 * m_editing_window_width; // m_control_width * 0.35;
double item_in_gap = 0.01 * m_editing_window_width;
double input_width = 0.29 * m_editing_window_width;
constexpr float UndefMinVal = -0.1f;
const float f_mm_to_in = static_cast<float>(GizmoObjectManipulation::mm_to_in);
ImGui::AlignTextToFramePadding();
m_imgui->text(label);
ImGui::SameLine(m_label_width);
ImGui::PushItemWidth(slider_with);
double left_width = m_label_width + slider_with + item_in_gap;
bool m_imperial_units = false;
float value = value_in;
float max_value = max_val;
if (m_imperial_units) {
value *= f_mm_to_in;
max_value *= f_mm_to_in;
}
float old_val = value;
const BoundingBoxf3 bbox = m_bounding_box;
//const float mean_size = float((bbox.size().x() + bbox.size().y() + bbox.size().z())) * (m_imperial_units ? f_mm_to_in : 1.f);
const float min_v = min_val > 0.f ? /*std::min(max_val, mean_size)*/ min_val : 1.f;
float min_size = value_in < 0.f ? UndefMinVal : min_v;
if (m_imperial_units) {
min_size *= f_mm_to_in;
}
std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _u8L("in") : "%.2f " + _u8L("mm");
m_imgui->bbl_slider_float_style(("##" + label).c_str(), &value, min_size, max_value, format.c_str());
ImGui::SameLine(left_width);
ImGui::PushItemWidth(input_width);
ImGui::BBLDragFloat(("##input_" + label).c_str(), &value, 0.05f, min_size, max_value, format.c_str());
value_in = value * float(m_imperial_units ? GizmoObjectManipulation::in_to_mm : 1.0);
return !is_approx(old_val, value);
}
void GLGizmoCut3D::render_move_center_input(int axis)
{
m_imgui->text(m_axis_names[axis]+":");
@@ -691,223 +744,299 @@ static double get_grabber_mean_size(const BoundingBoxf3& bb)
#endif
}
std::vector<Vec3i32> GLGizmoCut3D::offset_indices(const std::vector<Vec3i32>& base_indices, size_t vo)
{
std::vector<Vec3i32> offset;
int vo_int = static_cast<int>(vo);
for (const auto& tri : base_indices) {
offset.push_back({tri[0] + vo_int, tri[1] + vo_int, tri[2] + vo_int});
}
return offset;
}
indexed_triangle_set GLGizmoCut3D::its_make_groove_plane()
{
// values for calculation
// This function generates a dovetail slot in a wall, viewed from above (top-down).
// The slot has a wide "mouth" (closest to the wall surface) and a narrow "neck" (deepest into the wall).
// The flaps are the tapered sides connecting the mouth to the neck.
const float side_width = is_approx(m_groove.flaps_angle, 0.f) ? m_groove.depth : (m_groove.depth / sin(m_groove.flaps_angle));
const float flaps_width = 2.f * side_width * cos(m_groove.flaps_angle);
const float flap_taper_width = is_approx(m_groove.flaps_angle, 0.f) ? m_groove.depth : (m_groove.depth / sin(m_groove.flaps_angle));
const float total_flap_taper_width = 2.f * flap_taper_width * cos(m_groove.flaps_angle);
const float groove_half_width_upper = 0.5f * (m_groove.width);
const float groove_half_width_lower = 0.5f * (m_groove.width + flaps_width);
const float slot_neck_half_width = 0.5f * (m_groove.width);
const float slot_mouth_half_width = 0.5f * (m_groove.width + total_flap_taper_width);
const float cut_plane_radius = 1.5f * float(m_radius);
const float cut_plane_length = 1.5f * cut_plane_radius;
const float groove_half_depth = 0.5f * m_groove.depth;
const float plane_half_width = 0.5f * cut_plane_radius; // x
const float plane_half_height = 0.5f * cut_plane_length; // y
const float x = 0.5f * cut_plane_radius;
const float y = 0.5f * cut_plane_length;
float z_upper = groove_half_depth;
float z_lower = -groove_half_depth;
const float slot_half_depth = 0.5f * m_groove.depth; // z
float slot_front_z = slot_half_depth;
float slot_back_z = -slot_half_depth;
const float proj = y * tan(m_groove.angle);
const float flap_taper_offset = plane_half_height * tan(m_groove.angle);
float ext_upper_x = groove_half_width_upper + proj; // upper_x extension
float ext_lower_x = groove_half_width_lower + proj; // lower_x extension
float slot_mouth_outer_x = slot_neck_half_width + flap_taper_offset; // upper_x extension
float slot_neck_outer_x = slot_mouth_half_width + flap_taper_offset; // lower_x extension
float slot_outer_x_max = Max(slot_neck_outer_x, slot_mouth_outer_x); // max x extension
float nar_upper_x = groove_half_width_upper - proj; // upper_x narrowing
float nar_lower_x = groove_half_width_lower - proj; // lower_x narrowing
float slot_neck_inner_x = slot_neck_half_width - flap_taper_offset; // upper_x narrowing
float slot_mouth_inner_x = slot_mouth_half_width - flap_taper_offset; // lower_x narrowing
const float cut_plane_thiknes = 0.02f;// 0.02f * (float)get_grabber_mean_size(m_bounding_box); // cut_plane_thiknes
const float wall_thickness = 0.02f; // 0.02f * (float)get_grabber_mean_size(m_bounding_box); // cut_plane_thiknes
// Vertices of the groove used to detection if groove is valid
// They are written as:
// {left_ext_lower, left_nar_lower, left_ext_upper, left_nar_upper,
// right_ext_lower, right_nar_lower, right_ext_upper, right_nar_upper }
{
m_groove_vertices.clear();
m_groove_vertices.reserve(8);
m_groove_vertices.emplace_back(Vec3f(-ext_lower_x, -y, z_lower).cast<double>());
m_groove_vertices.emplace_back(Vec3f(-nar_lower_x, y, z_lower).cast<double>());
m_groove_vertices.emplace_back(Vec3f(-ext_upper_x, -y, z_upper).cast<double>());
m_groove_vertices.emplace_back(Vec3f(-nar_upper_x, y, z_upper).cast<double>());
m_groove_vertices.emplace_back(Vec3f( ext_lower_x, -y, z_lower).cast<double>());
m_groove_vertices.emplace_back(Vec3f( nar_lower_x, y, z_lower).cast<double>());
m_groove_vertices.emplace_back(Vec3f( ext_upper_x, -y, z_upper).cast<double>());
m_groove_vertices.emplace_back(Vec3f( nar_upper_x, y, z_upper).cast<double>());
}
// Different cases of groove plane:
// groove is open
if (groove_half_width_upper > proj && groove_half_width_lower > proj) {
indexed_triangle_set mesh;
auto get_vertices = [x, y](float z_upper, float z_lower, float nar_upper_x, float nar_lower_x, float ext_upper_x, float ext_lower_x) {
return std::vector<stl_vertex>({
// upper left part vertices
{-x, -y, z_upper}, {-x, y, z_upper}, {-nar_upper_x, y, z_upper}, {-ext_upper_x, -y, z_upper},
// lower part vertices
{-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower},
// upper right part vertices
{ext_upper_x, -y, z_upper}, {nar_upper_x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper}
});
};
mesh.vertices = get_vertices(z_upper, z_lower, nar_upper_x, nar_lower_x, ext_upper_x, ext_lower_x);
mesh.vertices.reserve(2 * mesh.vertices.size());
z_upper -= cut_plane_thiknes;
z_lower -= cut_plane_thiknes;
const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle);
nar_upper_x += under_x_shift;
nar_lower_x += under_x_shift;
ext_upper_x += under_x_shift;
ext_lower_x += under_x_shift;
std::vector<stl_vertex> vertices = get_vertices(z_upper, z_lower, nar_upper_x, nar_lower_x, ext_upper_x, ext_lower_x);
mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end());
mesh.indices = {
// above view
{5,4,7}, {5,7,6}, // lower part
{3,4,5}, {3,5,2}, // left side
{9,6,8}, {8,6,7}, // right side
{1,0,2}, {2,0,3}, // upper left part
{9,8,10}, {10,8,11}, // upper right part
// under view
{20,21,22}, {20,22,23}, // upper right part
{12,13,14}, {12,14,15}, // upper left part
{18,21,20}, {18,20,19}, // right side
{16,15,14}, {16,14,17}, // left side
{16,17,18}, {16,18,19}, // lower part
// left edge
{1,13,0}, {0,13,12},
// front edge
{0,12,3}, {3,12,15}, {3,15,4}, {4,15,16}, {4,16,7}, {7,16,19}, {7,19,20}, {7,20,8}, {8,20,11}, {11,20,23},
// right edge
{11,23,10}, {10,23,22},
// back edge
{1,13,2}, {2,13,14}, {2,14,17}, {2,17,5}, {5,17,6}, {6,17,18}, {6,18,9}, {9,18,21}, {9,21,10}, {10,21,22}
};
return mesh;
}
float cross_pt_upper_y = groove_half_width_upper / tan(m_groove.angle);
// groove is closed
if (groove_half_width_upper < proj && groove_half_width_lower < proj) {
float cross_pt_lower_y = groove_half_width_lower / tan(m_groove.angle);
indexed_triangle_set mesh;
auto get_vertices = [x, y](float z_upper, float z_lower, float cross_pt_upper_y, float cross_pt_lower_y, float ext_upper_x, float ext_lower_x) {
return std::vector<stl_vertex>({
// upper part vertices
{-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper},
{ext_upper_x, -y, z_upper}, {0.f, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper},
// lower part vertices
{-ext_lower_x, -y, z_lower}, {0.f, cross_pt_lower_y, z_lower}, {ext_lower_x, -y, z_lower}
});
};
mesh.vertices = get_vertices(z_upper, z_lower, cross_pt_upper_y, cross_pt_lower_y, ext_upper_x, ext_lower_x);
mesh.vertices.reserve(2 * mesh.vertices.size());
z_upper -= cut_plane_thiknes;
z_lower -= cut_plane_thiknes;
const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle);
cross_pt_upper_y += cut_plane_thiknes;
cross_pt_lower_y += cut_plane_thiknes;
ext_upper_x += under_x_shift;
ext_lower_x += under_x_shift;
std::vector<stl_vertex> vertices = get_vertices(z_upper, z_lower, cross_pt_upper_y, cross_pt_lower_y, ext_upper_x, ext_lower_x);
mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end());
mesh.indices = {
// above view
{8,7,9}, // lower part
{5,8,6}, {6,8,7}, // left side
{4,9,8}, {4,8,5}, // right side
{1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part
// under view
{10,11,16}, {16,11,15}, {15,11,12}, {15,12,14}, {14,12,13}, // upper part
{18,15,14}, {14,18,19}, // right side
{17,16,15}, {17,15,18}, // left side
{17,18,19}, // lower part
// left edge
{1,11,0}, {0,11,10},
// front edge
{0,10,6}, {6,10,16}, {6,17,16}, {6,7,17}, {7,17,19}, {7,19,9}, {4,14,19}, {4,19,9}, {4,14,13}, {4,13,3},
// right edge
{3,13,12}, {3,12,2},
// back edge
{2,12,11}, {2,11,1}
};
return mesh;
}
// groove is closed from the roof
int groove_count = this->m_groove_count;
float groove_gap = this->m_groove_gap;
indexed_triangle_set mesh;
mesh.vertices = {
// upper part vertices
{-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper},
{ext_upper_x, -y, z_upper}, {0.f, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper},
// lower part vertices
{-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower}
};
mesh.vertices.reserve(2 * mesh.vertices.size() + 1);
// handle multiple dovetails/grooves
for (int i = 0; i < groove_count; ++i) {
bool is_first_groove = i == 0; // when a groove is not the last groove, then limit the extent of the right plane so that it doesnt overlap the next groove
bool is_last_groove = i == groove_count - 1; // do the same in reverse if a groove is not the first groove
size_t vertex_index_offset = mesh.vertices.size();
z_upper -= cut_plane_thiknes;
z_lower -= cut_plane_thiknes;
// Calculate the x-axis offset for this dovetail
float groove_offset_factor_start = -.5 * ((groove_count - 1));
float groove_offset_factor = groove_offset_factor_start + i;
// Recalculate x with offset (only X-axis offset)
float offset_x = groove_offset_factor * (groove_gap + (2 * slot_outer_x_max));
const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle);
// Vertices of the groove used to detection if groove is valid (not used in mesh)
{
m_groove_vertices.clear();
m_groove_vertices.reserve(8);
nar_lower_x += under_x_shift;
ext_upper_x += under_x_shift;
ext_lower_x += under_x_shift;
m_groove_vertices.emplace_back(Vec3f(-slot_neck_outer_x, -plane_half_height, slot_front_z).cast<double>());
m_groove_vertices.emplace_back(Vec3f(-slot_mouth_inner_x, plane_half_height, slot_front_z).cast<double>());
m_groove_vertices.emplace_back(Vec3f(-slot_mouth_outer_x, -plane_half_height, slot_back_z).cast<double>());
m_groove_vertices.emplace_back(Vec3f(-slot_neck_outer_x, plane_half_height, slot_back_z).cast<double>());
m_groove_vertices.emplace_back(Vec3f(slot_neck_outer_x, -plane_half_height, slot_front_z).cast<double>());
m_groove_vertices.emplace_back(Vec3f(slot_mouth_inner_x, plane_half_height, slot_front_z).cast<double>());
m_groove_vertices.emplace_back(Vec3f(slot_mouth_outer_x, -plane_half_height, slot_back_z).cast<double>());
m_groove_vertices.emplace_back(Vec3f(slot_neck_outer_x, plane_half_height, slot_back_z).cast<double>());
}
std::vector<stl_vertex> vertices = {
// upper part vertices
{-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper},
{ext_upper_x, -y, z_upper}, {under_x_shift, cross_pt_upper_y, z_upper}, {-under_x_shift, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper},
// lower part vertices
{-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower}
};
mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end());
// ___
// Case: Groove is open (Top&bottom: __\ /__ )
if (slot_neck_half_width > flap_taper_offset && slot_mouth_half_width > flap_taper_offset) {
auto get_vertices = [plane_half_width, plane_half_height]
(float slot_front_z, float slot_back_z, float slot_neck_inner_x, float slot_mouth_inner_x, float slot_mouth_outer_x, float slot_neck_outer_x,
float slot_outer_x_max, bool is_first_groove, bool is_last_groove, float groove_gap, float offset_x)
{
mesh.indices = {
// above view
{8,7,10}, {8,10,9}, // lower part
{5,8,7}, {5,7,6}, // left side
{4,10,9}, {4,9,5}, // right side
{1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part
// under view
{11,12,18}, {18,12,17}, {17,12,16}, {16,12,13}, {16,13,15}, {15,13,14}, // upper part
{21,16,15}, {21,15,22}, // right side
{19,18,17}, {19,17,20}, // left side
{19,20,21}, {19,21,22}, // lower part
// left edge
{1,12,11}, {1,11,0},
// front edge
{0,11,18}, {0,18,6}, {7,19,18}, {7,18,6}, {7,19,22}, {7,22,10}, {10,22,15}, {10,15,4}, {4,15,14}, {4,14,3},
// right edge
{3,14,13}, {3,14,2},
// back edge
{2,13,12}, {2,12,1}, {5,16,21}, {5,21,9}, {9,21,20}, {9,20,8}, {5,17,20}, {5,20,8}
};
return std::vector<stl_vertex>({
// front left part vertices
{is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, -plane_half_height, slot_front_z}, // *__/ \__
{is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, plane_half_height, slot_front_z}, // *__/ \__
{-slot_neck_inner_x + offset_x, plane_half_height, slot_front_z}, // __*/ \__
{-slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z}, // __/* \__
// back part vertices
{-slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}, // __*/ \__
{-slot_mouth_inner_x + offset_x, plane_half_height, slot_back_z}, // __/* \__
{slot_mouth_inner_x + offset_x, plane_half_height, slot_back_z}, // __/ *\__
{slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}, // __/ \*__
// front right part vertices
{slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z}, // __/ \*__
{slot_neck_inner_x + offset_x, plane_half_height, slot_front_z}, // __/ *\__
{is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, plane_half_height, slot_front_z}, // __/ \__*
{is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, -plane_half_height, slot_front_z}}); // __/ \__*
};
std::vector<stl_vertex> vertices = get_vertices(slot_front_z, slot_back_z, slot_neck_inner_x, slot_mouth_inner_x, slot_mouth_outer_x, slot_neck_outer_x, slot_outer_x_max,
is_first_groove, is_last_groove, groove_gap, offset_x);
mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end());
// Back face
slot_front_z -= wall_thickness;
slot_back_z -= wall_thickness;
const float slot_back_face_x_offset = wall_thickness / tan(0.5f * m_groove.flaps_angle);
slot_neck_inner_x += slot_back_face_x_offset;
slot_mouth_inner_x += slot_back_face_x_offset;
slot_mouth_outer_x += slot_back_face_x_offset;
slot_neck_outer_x += slot_back_face_x_offset;
vertices = get_vertices(slot_front_z, slot_back_z, slot_neck_inner_x, slot_mouth_inner_x, slot_mouth_outer_x, slot_neck_outer_x,
slot_outer_x_max, is_first_groove,
is_last_groove, groove_gap, offset_x);
mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end());
std::vector<Vec3i32> base_indices;
base_indices = {
// above view
{5,4,7}, {5,7,6}, // lower part
{3,4,5}, {3,5,2}, // left side
{9,6,8}, {8,6,7}, // right side
{1,0,2}, {2,0,3}, // upper left part
{9,8,10}, {10,8,11}, // upper right part
// under view
{20,21,22}, {20,22,23}, // upper right part
{12,13,14}, {12,14,15}, // upper left part
{18,21,20}, {18,20,19}, // right side
{16,15,14}, {16,14,17}, // left side
{16,17,18}, {16,18,19}, // lower part
// left edge
{1,13,0}, {0,13,12},
// front edge
{0,12,3}, {3,12,15}, {3,15,4}, {4,15,16}, {4,16,7}, {7,16,19}, {7,19,20}, {7,20,8}, {8,20,11}, {11,20,23},
// right edge
{11,23,10}, {10,23,22},
// back edge
{1,13,2}, {2,13,14}, {2,14,17}, {2,17,5}, {5,17,6}, {6,17,18}, {6,18,9}, {9,18,21}, {9,21,10}, {10,21,22}
};
std::vector<Vec3i32> indices = offset_indices(base_indices, vertex_index_offset);
mesh.indices.insert(mesh.indices.end(), indices.begin(), indices.end());
}
// __
// CASE: Groove is closed (Top&Bottom: ___/ \___)
else if (slot_neck_half_width < flap_taper_offset && slot_mouth_half_width < flap_taper_offset) {
float slot_neck_apex_y = slot_neck_half_width / tan(m_groove.angle);
float slot_mouth_apex_y = slot_mouth_half_width / tan(m_groove.angle);
auto get_vertices = [plane_half_width, plane_half_height]
(float slot_front_z, float slot_back_z, float slot_neck_apex_y, float slot_mouth_apex_y, float slot_mouth_outer_x, float slot_neck_outer_x,
float slot_outer_x_max, bool is_first_groove, bool is_last_groove, float groove_gap, float offset_x)
{
return std::vector<stl_vertex>({
// front part vertices
{is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, -plane_half_height, slot_front_z}, // *__/\__
{is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, plane_half_height, slot_front_z}, // *__/\__
{is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, plane_half_height, slot_front_z}, // __/\__*
{is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, -plane_half_height, slot_front_z}, // __/\__*
{slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z}, // __*/\__
{offset_x, slot_neck_apex_y, slot_front_z}, // __/*\__
{-slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z}, // __/\*__
// back part vertices
{-slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}, // __*/\__
{offset_x, slot_mouth_apex_y, slot_back_z}, // __/*\__
{slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}}); // __/\*__
};
std::vector<stl_vertex> vertices = get_vertices(slot_front_z, slot_back_z, slot_neck_apex_y, slot_mouth_apex_y,
slot_mouth_outer_x, slot_neck_outer_x, slot_outer_x_max,
is_first_groove, is_last_groove, groove_gap, offset_x);
mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end());
// Back face
slot_front_z -= wall_thickness;
slot_back_z -= wall_thickness;
slot_neck_apex_y += wall_thickness;
slot_mouth_apex_y += wall_thickness;
const float slot_back_face_x_offset = wall_thickness / tan(0.5f * m_groove.flaps_angle);
slot_mouth_outer_x += slot_back_face_x_offset;
slot_neck_outer_x += slot_back_face_x_offset;
vertices = get_vertices(slot_front_z, slot_back_z, slot_neck_apex_y, slot_mouth_apex_y, slot_mouth_outer_x, slot_neck_outer_x,
slot_outer_x_max, is_first_groove,
is_last_groove, groove_gap, offset_x);
mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end());
std::vector<Vec3i32> base_indices = {
// above view
{8,7,9}, // lower part
{5,8,6}, {6,8,7}, // left side
{4,9,8}, {4,8,5}, // right side
{1,0,6}, {1,6,5},{1,5,2},
{2,5,4}, {2,4,3}, // upper part
// under view
{10,11,16}, {16,11,15},
{15,11,12}, {15,12,14}, {14,12,13}, // upper part
{18,15,14}, {14,18,19}, // right side
{17,16,15}, {17,15,18}, // left side
{17,18,19}, // lower part
// left edge
{1,11,0}, {0,11,10},
// front edge
{0,10,6}, {6,10,16}, {6,17,16}, {6,7,17}, {7,17,19}, {7,19,9}, {4,14,19}, {4,19,9}, {4,14,13}, {4,13,3},
// right edge
{3,13,12}, {3,12,2},
// back edge
{2,12,11}, {2,11,1}
};
std::vector<Vec3i32> indices = offset_indices(base_indices, vertex_index_offset);
mesh.indices.insert(mesh.indices.end(), indices.begin(), indices.end());
}
// Case: Groove is closed from the roof (TOP&Bottom: __/\__ )
else {
float slot_neck_apex_y = slot_neck_half_width / tan(m_groove.angle);
std::vector<stl_vertex> vertices = {
// front part vertices
{is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, -plane_half_height, slot_front_z},
{is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, plane_half_height, slot_front_z},
{is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, plane_half_height, slot_front_z},
{is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, -plane_half_height, slot_front_z},
{slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z},
{offset_x, slot_neck_apex_y, slot_front_z},
{-slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z},
// back part vertices
{-slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z},
{-slot_mouth_inner_x + offset_x, plane_half_height, slot_back_z},
{slot_mouth_inner_x + offset_x, plane_half_height, slot_back_z},
{slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}};
mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end());
// Back face
slot_front_z -= wall_thickness;
slot_back_z -= wall_thickness;
const float slot_back_face_x_offset = wall_thickness / tan(0.5f * m_groove.flaps_angle);
slot_mouth_inner_x += slot_back_face_x_offset;
slot_mouth_outer_x += slot_back_face_x_offset;
slot_neck_outer_x += slot_back_face_x_offset;
vertices = {
// upper part vertices
{is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, -plane_half_height, slot_front_z},
{is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, plane_half_height, slot_front_z},
{is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, plane_half_height, slot_front_z},
{is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, -plane_half_height, slot_front_z},
{slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z},
{slot_back_face_x_offset + offset_x, slot_neck_apex_y, slot_front_z},
{-slot_back_face_x_offset + offset_x, slot_neck_apex_y, slot_front_z},
{-slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z},
// lower part vertices
{-slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z},
{-slot_mouth_inner_x + offset_x, plane_half_height, slot_back_z},
{slot_mouth_inner_x + offset_x, plane_half_height, slot_back_z},
{slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}};
mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end());
// Indices for this dovetail
std::vector<Vec3i32> base_indices = {
// above view
{8,7,10}, {8,10,9}, // lower part
{5,8,7}, {5,7,6}, // left side
{4,10,9}, {4,9,5}, // right side
{1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part
// under view
{11,12,18}, {18,12,17}, {17,12,16}, {16,12,13}, {16,13,15}, {15,13,14}, // upper part
{21,16,15}, {21,15,22}, // right side
{19,18,17}, {19,17,20}, // left side
{19,20,21}, {19,21,22}, // lower part
// left edge
{1,12,11}, {1,11,0},
// front edge
{0,11,18}, {0,18,6}, {7,19,18}, {7,18,6}, {7,19,22}, {7,22,10}, {10,22,15}, {10,15,4}, {4,15,14}, {4,14,3},
// right edge
{3,14,13}, {3,14,2},
// back edge
{2,13,12}, {2,12,1}, {5,16,21}, {5,21,9}, {9,21,20}, {9,20,8}, {5,17,20}, {5,20,8}
};
std::vector<Vec3i32> indices = offset_indices(base_indices, vertex_index_offset);
mesh.indices.insert(mesh.indices.end(), indices.begin(), indices.end());
}
}
return mesh;
}
@@ -1908,7 +2037,7 @@ GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transfor
// split to parts
for (int id = int(volumes.size())-1; id >= 0; id--)
if (volumes[id]->is_splittable())
volumes[id]->split(1);
volumes[id]->split(1, false); // No need to remap paint here, we do it later in perform_by_contour
m_parts.clear();
for (const ModelVolume* volume : volumes) {
@@ -2240,7 +2369,10 @@ void GLGizmoCut3D::apply_selected_connectors(std::function<void(size_t idx)> app
void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors, float x, float y, float bottom_limit)
{
// Connectors section
float f_scale = m_parent.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
ImGui::Separator();
// WIP : Auto : Need to implement
@@ -2290,7 +2422,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors, flo
m_imgui->disabled_end();
const float depth_min_value = m_connector_type == CutConnectorType::Snap ? m_connector_size : -0.1f;
if (render_slider_double_input(m_labels_map["Depth"], m_connector_depth_ratio, m_connector_depth_ratio_tolerance, depth_min_value))
if (render_slider_two_input(m_labels_map["Depth"], m_connector_depth_ratio, m_connector_depth_ratio_tolerance, depth_min_value))
apply_selected_connectors([this, &connectors](size_t idx) {
if (m_connector_depth_ratio > 0)
connectors[idx].height = m_connector_depth_ratio;
@@ -2298,7 +2430,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors, flo
connectors[idx].height_tolerance = m_connector_depth_ratio_tolerance;
});
if (render_slider_double_input(m_labels_map["Size"], m_connector_size, m_connector_size_tolerance))
if (render_slider_two_input(m_labels_map["Size"], m_connector_size, m_connector_size_tolerance))
apply_selected_connectors([this, &connectors](size_t idx) {
if (m_connector_size > 0)
connectors[idx].radius = 0.5f * m_connector_size;
@@ -2318,26 +2450,24 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors, flo
ImGui::Separator();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f));
render_tooltip_button(x, y);
float f_scale = m_parent.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
ImGui::SameLine();
GLGizmoUtils::begin_right_aligned_buttons({_L("Confirm connectors"), _L("Cancel")});
GLGizmoUtils::push_orca_button_style();
if (m_imgui->button(_L("Confirm connectors"))) {
unselect_all_connectors();
set_connectors_editing(false);
}
GLGizmoUtils::pop_orca_button_style();
ImGui::SameLine(m_label_width + m_editing_window_width - m_imgui->calc_text_size(_L("Cancel")).x - m_imgui->get_style_scaling() * 8);
ImGui::SameLine();
if (m_imgui->button(_L("Cancel"))) {
reset_connectors();
set_connectors_editing(false);
}
ImGui::PopStyleVar(2);
ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding
}
void GLGizmoCut3D::render_build_size()
@@ -2430,7 +2560,7 @@ void GLGizmoCut3D::process_contours()
if (CutMode(m_mode) == CutMode::cutTongueAndGroove) {
if (has_valid_groove()) {
Cut cut(model_objects[object_idx], instance_idx, get_cut_matrix(selection));
const ModelObjectPtrs& new_objects = cut.perform_with_groove(m_groove, m_rotation_m, true);
const ModelObjectPtrs& new_objects = cut.perform_with_groove(m_groove, m_rotation_m, m_groove_count, m_groove_gap, m_radius, true);
if (!new_objects.empty())
m_part_selection = PartSelection(new_objects.front(), instance_idx);
}
@@ -2485,13 +2615,13 @@ void GLGizmoCut3D::render_color_marker(float size, const ImU32& color)
ImGui::SameLine();
}
void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in_val, const float& init_val, float& in_tolerance)
void GLGizmoCut3D::render_groove_two_float_input(const std::string& label, float& in_val, const float& init_val, float& in_tolerance)
{
bool is_changed{false};
float val = in_val;
float tolerance = in_tolerance;
if (render_slider_double_input(label, val, tolerance, -0.1f, std::min(0.3f*in_val, 1.5f))) {
if (render_slider_two_input(label, val, tolerance, -0.1f, std::min(0.3f*in_val, 1.5f))) {
if (m_imgui->get_last_slider_status().can_take_snapshot) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), GUI::format("%1%: %2%", _u8L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction);
m_imgui->get_last_slider_status().invalidate_snapshot();
@@ -2525,6 +2655,82 @@ void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in
}
}
void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in_val, const float& init_val, const bool disabled)
{
if (disabled)
m_imgui->disabled_begin(true);
bool is_changed{false};
Vec2d plate_size = wxGetApp().plater()->get_partplate_list().get_plate(0)->get_size();
float max_val = std::max(plate_size.x(), plate_size.y()) / (std::max(m_groove_count - 1, 1));
float val = in_val;
if (render_slider_input(label, val, -0.1f, max_val)) {
if (m_imgui->get_last_slider_status().can_take_snapshot) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), GUI::format("%1%: %2%", _u8L("Groove change"), label),
UndoRedo::SnapshotType::GizmoAction);
m_imgui->get_last_slider_status().invalidate_snapshot();
m_groove_editing = true;
}
in_val = val;
is_changed = true;
}
ImGui::SameLine();
if (!disabled) m_imgui->disabled_begin(is_approx(in_val, init_val) );
const std::string act_name = _u8L("Reset");
if (render_reset_button("##groove_" + label + act_name, act_name)) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), GUI::format("%1%: %2%", act_name, label), UndoRedo::SnapshotType::GizmoAction);
in_val = init_val;
is_changed = true;
}
if (!disabled) m_imgui->disabled_end();
if (is_changed) {
update_plane_model();
reset_cut_by_contours();
}
if (m_is_slider_editing_done) {
m_groove_editing = false;
reset_cut_by_contours();
}
if (disabled)
m_imgui->disabled_end();
}
void GLGizmoCut3D::render_groove_int_input(const std::string& label, int& in_val, const int& init_val, int min_val, int max_val)
{
bool is_changed{false};
m_imgui->text(label);
ImGui::AlignTextToFramePadding();
ImGui::SameLine(m_label_width);
ImGui::PushItemWidth(m_control_width);
if (ImGui::InputInt("##int_input", &in_val, 1, 5)) {
in_val = std::clamp(in_val, min_val, max_val);
is_changed = true;
}
ImGui::SameLine();
m_imgui->disabled_begin(in_val == init_val);
const std::string act_name = _u8L("Reset");
if (render_reset_button("##int_" + label + act_name, act_name)) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), GUI::format("%1%: %2%", act_name, label), UndoRedo::SnapshotType::GizmoAction);
in_val = init_val;
is_changed = true;
}
m_imgui->disabled_end();
if (is_changed) {
update_plane_model();
reset_cut_by_contours();
}
}
bool GLGizmoCut3D::render_angle_input(const std::string& label, float& in_val, const float& init_val, float min_val, float max_val)
{
// -------- [ ]
@@ -2641,10 +2847,14 @@ void GLGizmoCut3D::render_snap_specific_input(const std::string& label, const wx
void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors, float x, float y, float bottom_limit)
{
// if (m_mode == size_t(CutMode::cutPlanar)) {
const bool has_connectors = !connectors.empty();
const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && m_bb_center.isApprox(m_plane_center);
float f_scale = m_parent.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
CutMode mode = CutMode(m_mode);
if (mode == CutMode::cutPlanar || mode == CutMode::cutTongueAndGroove) {
const bool has_connectors = !connectors.empty();
m_imgui->disabled_begin(has_connectors);
if (render_cut_mode_combo())
@@ -2659,7 +2869,6 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors, floa
render_move_center_input(Z);
ImGui::SameLine();
const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && m_bb_center.isApprox(m_plane_center);
m_imgui->disabled_begin(is_cut_plane_init);
std::string act_name = _u8L("Reset cutting plane");
if (render_reset_button("cut_plane", act_name)) {
@@ -2668,35 +2877,36 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors, floa
}
m_imgui->disabled_end();
// render_flip_plane_button();
if (mode == CutMode::cutPlanar) {
add_vertical_scaled_interval(0.75f);
ImGui::Separator();
m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower || m_keep_as_parts || (m_part_selection.valid() && m_part_selection.is_one_object()));
if (m_imgui->button(has_connectors ? _L("Edit connectors") : _L("Add connectors")))
set_connectors_editing(true);
m_imgui->disabled_end();
ImGui::SameLine(1.5f * m_control_width);
m_imgui->disabled_begin(is_cut_plane_init && !has_connectors);
act_name = _u8L("Reset cut");
if (m_imgui->button(wxString::FromUTF8(act_name), _L("Reset cutting plane and remove connectors"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), act_name, UndoRedo::SnapshotType::GizmoAction);
reset_cut_plane();
reset_connectors();
}
m_imgui->disabled_end();
}
else if (mode == CutMode::cutTongueAndGroove) {
m_is_slider_editing_done = false;
ImGui::Separator();
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, m_labels_map["Groove"] + ": ");
render_groove_float_input(m_labels_map["Depth"], m_groove.depth, m_groove.depth_init, m_groove.depth_tolerance);
render_groove_float_input(m_labels_map["Width"], m_groove.width, m_groove.width_init, m_groove.width_tolerance);
render_groove_two_float_input(m_labels_map["Depth"], m_groove.depth, m_groove.depth_init, m_groove.depth_tolerance);
render_groove_two_float_input(m_labels_map["Width"], m_groove.width, m_groove.width_init, m_groove.width_tolerance);
render_groove_angle_input(m_labels_map["Flap Angle"], m_groove.flaps_angle, m_groove.flaps_angle_init, 30.f, 120.f);
render_groove_angle_input(m_labels_map["Groove Angle"], m_groove.angle, m_groove.angle_init, 0.f, 15.f);
ImGui::Spacing();
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, m_labels_map["Multiple"] + ": ");
render_groove_int_input(m_labels_map["Count"], m_groove_count, m_groove_count_init , 1, 100);
render_groove_float_input(m_labels_map["Gap"], m_groove_gap, m_groove_gap_init, m_groove_count == 1);
m_imgui->disabled_begin(true);
const float groove_width = Cut::calculate_groove_width(m_groove, m_radius);
m_imgui->text(m_labels_map["Spacing"]);
ImGui::SameLine(m_label_width);
std::ostringstream oss;
oss << std::fixed << std::setprecision(2) << (m_groove_gap + groove_width);
m_imgui->text(oss.str() + "mm");
m_imgui->disabled_end();
}
ImGui::Separator();
@@ -2759,21 +2969,35 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors, floa
}
ImGui::Separator();
render_tooltip_button(x, y);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f));
float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y;
render_tooltip_button(x, get_cur_y);
float f_scale = m_parent.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
if (mode == CutMode::cutPlanar) {
ImGui::SameLine();
m_imgui->disabled_begin(is_cut_plane_init && !has_connectors);
if (m_imgui->button(_L("Reset"), _L("Reset cutting plane and remove connectors"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset Cut", UndoRedo::SnapshotType::GizmoAction);
reset_cut_plane();
reset_connectors();
}
m_imgui->disabled_end();
}
ImGui::SameLine();
GLGizmoUtils::begin_right_aligned_buttons({_L("Perform cut"), _L("Cancel")});
m_imgui->disabled_begin(!can_perform_cut());
if(m_imgui->button(_L("Perform cut")))
perform_cut(m_parent.get_selection());
GLGizmoUtils::push_orca_button_style();
if(m_imgui->button(_L("Perform cut")))
perform_cut(m_parent.get_selection());
GLGizmoUtils::pop_orca_button_style();
m_imgui->disabled_end();
ImGui::PopStyleVar(2);
ImGui::SameLine();
if (m_imgui->button(_L("Cancel"))) {
m_parent.reset_all_gizmos();
}
ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding
}
void GLGizmoCut3D::validate_connector_settings()
@@ -2876,7 +3100,16 @@ void GLGizmoCut3D::init_input_window_data(CutConnectors &connectors)
void GLGizmoCut3D::render_input_window_warning() const
{
if (! m_invalid_connectors_idxs.empty()) {
const bool invalid_connector_warning = !m_invalid_connectors_idxs.empty();
const bool keep_after_cut_warning = !m_keep_upper && !m_keep_lower;
const bool invalid_contour_warning = !has_valid_contour();
const bool invalid_groove_warning = !has_valid_groove();
if (invalid_connector_warning || keep_after_cut_warning || invalid_contour_warning || invalid_groove_warning) {
ImGui::Separator();
}
if (invalid_connector_warning) {
wxString out = /*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Invalid connectors detected") + ":";
if (m_info_stats.outside_cut_contour > size_t(0))
out += "\n - " + format_wxstr(_L_PLURAL("%1$d connector is out of cut contour", "%1$d connectors are out of cut contour", m_info_stats.outside_cut_contour),
@@ -2886,14 +3119,14 @@ void GLGizmoCut3D::render_input_window_warning() const
m_info_stats.outside_bb);
if (m_info_stats.is_overlap)
out += "\n - " + _L("Some connectors are overlapped");
m_imgui->text(out);
m_imgui->warning_text(out);
}
if (!m_keep_upper && !m_keep_lower)
m_imgui->text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Select at least one object to keep after cutting."));
if (!has_valid_contour())
m_imgui->text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Cut plane is placed out of object"));
else if (!has_valid_groove())
m_imgui->text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Cut plane with groove is invalid"));
if (keep_after_cut_warning)
m_imgui->warning_text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Select at least one object to keep after cutting."));
if (invalid_contour_warning)
m_imgui->warning_text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Cut plane is placed out of object"));
else if (invalid_groove_warning)
m_imgui->warning_text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Cut plane with groove is invalid"));
}
void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit)
@@ -3321,6 +3554,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
wxBusyCursor wait;
const bool keep_painting = GUI::wxGetApp().app_config->get_bool("keep_painting");
ModelObjectCutAttributes attributes = only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) |
only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) |
only_if(has_connectors ? false : m_keep_as_parts, ModelObjectCutAttribute::KeepAsParts) |
@@ -3329,14 +3563,15 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) |
only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) |
only_if(dowels_count > 0, ModelObjectCutAttribute::CreateDowels) |
only_if(!has_connectors && !cut_with_groove && cut_mo->cut_id.id().invalid(), ModelObjectCutAttribute::InvalidateCutInfo);
only_if(!has_connectors && !cut_with_groove && cut_mo->cut_id.id().invalid(), ModelObjectCutAttribute::InvalidateCutInfo) |
only_if(keep_painting, ModelObjectCutAttribute::KeepPaint);
// update cut_id for the cut object in respect to the attributes
update_object_cut_id(cut_mo->cut_id, attributes, dowels_count);
Cut cut(cut_mo, instance_idx, get_cut_matrix(selection), attributes);
const ModelObjectPtrs& new_objects = cut_by_contour ? cut.perform_by_contour(m_part_selection.get_cut_parts(), dowels_count):
cut_with_groove ? cut.perform_with_groove(m_groove, m_rotation_m) :
const ModelObjectPtrs& new_objects = cut_by_contour ? cut.perform_by_contour(mo, m_part_selection.get_cut_parts(), dowels_count):
cut_with_groove ? cut.perform_with_groove(m_groove, m_rotation_m, m_groove_count, m_groove_gap, m_radius) :
cut.perform_with_plane();
// fix_non_manifold_edges
@@ -3362,12 +3597,12 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
// model_name failing reason
std::vector<std::pair<std::string, std::string>> failed_models;
auto plater = wxGetApp().plater();
auto fix_and_update_progress = [this, plater](ModelObject *model_object, const int vol_idx, const string &model_name, ProgressDialog &progress_dlg,
auto fix_and_update_progress = [this, plater, keep_painting](ModelObject *model_object, const int vol_idx, const string &model_name, ProgressDialog &progress_dlg,
std::vector<std::string> &succes_models, std::vector<std::pair<std::string, std::string>> &failed_models) {
wxString msg = _L("Repairing model object");
msg += ": " + from_u8(model_name) + "\n";
std::string res;
if (!fix_model_with_cgal_gui(*model_object, vol_idx, progress_dlg, msg, res)) return false;
if (!fix_model_with_cgal_gui(*model_object, vol_idx, progress_dlg, msg, res, keep_painting)) return false;
return true;
};
ProgressDialog progress_dlg(_L("Repairing model object"), "", 100, find_toplevel_parent(plater), wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT, true);
+12 -2
View File
@@ -124,6 +124,12 @@ class GLGizmoCut3D : public GLGizmoBase
bool m_is_slider_editing_done { false };
// Multiple Dovetail cuts
int m_groove_count_init { 1 };
int m_groove_count { 1 };
float m_groove_gap { 10.f }; // distance between multiple dovetail cuts
float m_groove_gap_init { 10.f };
// Input params for cut with snaps
float m_snap_bulge_proportion{ 0.15f };
float m_snap_space_proportion{ 0.3f };
@@ -301,8 +307,10 @@ protected:
void add_horizontal_scaled_interval(float interval);
void add_horizontal_shift(float shift);
void render_color_marker(float size, const ImU32& color);
void render_groove_float_input(const std::string &label, float &in_val, const float &init_val, float &in_tolerance);
void render_groove_two_float_input(const std::string &label, float &in_val, const float &init_val, float &in_tolerance);
void render_groove_float_input(const std::string &label, float &in_val, const float &init_val, const bool disabled);
void render_groove_angle_input(const std::string &label, float &in_val, const float &init_val, float min_val, float max_val);
void render_groove_int_input(const std::string& label, int& in_val, const int& init_val, int min_val, int max_val);
bool render_angle_input(const std::string& label, float& in_val, const float& init_val, float min_val, float max_val);
void render_snap_specific_input(const std::string& label, const wxString& tooltip, float& in_val, const float& init_val, const float min_val, const float max_val);
void render_cut_plane_input_window(CutConnectors &connectors, float x, float y, float bottom_limit);
@@ -338,7 +346,8 @@ private:
void switch_to_mode(size_t new_mode);
bool render_cut_mode_combo();
bool render_double_input(const std::string& label, double& value_in);
bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float min_val = -0.1f, float max_tolerance = -0.1f);
bool render_slider_two_input(const std::string& label, float& value_in, float& tolerance_in, float min_val = -0.1f, float max_tolerance = -0.1f);
bool render_slider_input(const std::string& label, float& value_in, float min_val = -0.1f, float max_val = 100.f);
void render_move_center_input(int axis);
void render_connect_mode_radio_button(CutConnectorMode mode);
bool render_reset_button(const std::string& label_id, const std::string& tooltip) const;
@@ -378,6 +387,7 @@ private:
void toggle_model_objects_visibility();
std::vector<Vec3i32> offset_indices(const std::vector<Vec3i32>& base_indices, size_t vo);
indexed_triangle_set its_make_groove_plane();
indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes);
+73 -28
View File
@@ -1267,6 +1267,59 @@ void GLGizmoEmboss::reset_volume()
remove_notification_not_valid_font();
}
namespace {
// FIX IT: It should not change volume position before successfull change volume by process
void fix_transformation(const StyleManager::Style& from, const StyleManager::Style& to, GLCanvas3D& canvas)
{
// fix Z rotation when exists difference in styles
const std::optional<float>& f_angle_opt = from.angle;
const std::optional<float>& t_angle_opt = to.angle;
if (!is_approx(f_angle_opt, t_angle_opt)) {
// fix rotation
float f_angle = f_angle_opt.value_or(.0f);
float t_angle = t_angle_opt.value_or(.0f);
do_local_z_rotate(canvas.get_selection(), t_angle - f_angle);
std::string no_snapshot;
canvas.do_rotate(no_snapshot);
}
// fix distance (Z move) when exists difference in styles
const std::optional<float>& f_move_opt = from.distance;
const std::optional<float>& t_move_opt = to.distance;
if (!is_approx(f_move_opt, t_move_opt)) {
float f_move = f_move_opt.value_or(.0f);
float t_move = t_move_opt.value_or(.0f);
do_local_z_move(canvas.get_selection(), t_move - f_move);
std::string no_snapshot;
canvas.do_move(no_snapshot);
}
}
} // namespace
bool GLGizmoEmboss::is_changed_from_default_style()
{
int default_index = 0;
const StyleManager::Style& default_style = m_style_manager.get_styles()[default_index];
const StyleManager::Style& current_style = m_style_manager.get_style();
return default_style == current_style;
}
void GLGizmoEmboss::reset_to_default_style()
{
int default_index = 0;
const StyleManager::Style& current_style = m_style_manager.get_style();
const StyleManager::Style& default_style = m_style_manager.get_styles()[default_index];
StyleManager::Style cur_s = current_style; // copy
StyleManager::Style new_s = default_style; // copy
if (m_style_manager.load_style(default_index)) {
::fix_transformation(cur_s, new_s, m_parent);
process();
}
}
void GLGizmoEmboss::calculate_scale() {
Transform3d to_world = m_parent.get_selection().get_first_volume()->world_matrix();
auto to_world_linear = to_world.linear();
@@ -1406,9 +1459,29 @@ void GLGizmoEmboss::draw_window(float x, float y)
draw_model_type();
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
float f_scale = m_parent.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y);
ImGui::SameLine();
m_imgui->disabled_begin(is_changed_from_default_style());
if (m_imgui->button(_L("Reset"), _L("Reset all options except the text and operation"))) {
reset_to_default_style();
}
m_imgui->disabled_end();
ImGui::SameLine();
GLGizmoUtils::begin_right_aligned_buttons({_L("Done")});
if (m_imgui->button(_L("Done"))) {
m_parent.reset_all_gizmos();
}
ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding
#ifdef SHOW_WX_FONT_DESCRIPTOR
if (is_selected_style)
@@ -2155,34 +2228,6 @@ void GLGizmoEmboss::draw_delete_style_button() {
}
}
namespace {
// FIX IT: It should not change volume position before successfull change volume by process
void fix_transformation(const StyleManager::Style &from, const StyleManager::Style &to, GLCanvas3D &canvas) {
// fix Z rotation when exists difference in styles
const std::optional<float> &f_angle_opt = from.angle;
const std::optional<float> &t_angle_opt = to.angle;
if (!is_approx(f_angle_opt, t_angle_opt)) {
// fix rotation
float f_angle = f_angle_opt.value_or(.0f);
float t_angle = t_angle_opt.value_or(.0f);
do_local_z_rotate(canvas.get_selection(), t_angle - f_angle);
std::string no_snapshot;
canvas.do_rotate(no_snapshot);
}
// fix distance (Z move) when exists difference in styles
const std::optional<float> &f_move_opt = from.distance;
const std::optional<float> &t_move_opt = to.distance;
if (!is_approx(f_move_opt, t_move_opt)) {
float f_move = f_move_opt.value_or(.0f);
float t_move = t_move_opt.value_or(.0f);
do_local_z_move(canvas.get_selection(), t_move - f_move);
std::string no_snapshot;
canvas.do_move(no_snapshot);
}
}
} // namesapce
void GLGizmoEmboss::draw_style_list() {
if (!m_style_manager.is_active_font()) return;
+3
View File
@@ -109,6 +109,9 @@ private:
void set_volume_by_selection();
void reset_volume();
bool is_changed_from_default_style();
void reset_to_default_style();
// create volume from text - main functionality
bool process(bool make_snapshot = true);
void close();
+34 -31
View File
@@ -82,7 +82,7 @@ bool GLGizmoFdmSupports::on_init()
m_desc["perform"] = _L("Perform");
m_desc["on_overhangs_only"] = _L("On highlighted overhangs only");
m_desc["remove_all"] = _L("Erase all painting");
m_desc["remove_all"] = _L("Erase all");
m_desc["highlight_by_angle"] = _L("Highlight overhang areas");
m_desc["tool_type"] = _L("Tool type");
m_desc["gap_fill"] = _L("Gap fill");
@@ -215,6 +215,9 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
//BBS
ImGuiWrapper::push_toolbar_style(m_parent.get_scale());
float f_scale = m_parent.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
@@ -225,11 +228,10 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
const float highlight_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle")).x + m_imgui->scaled(1.5f);
const float reset_button_slider_left = m_imgui->calc_text_size(m_desc.at("reset_direction")).x + m_imgui->scaled(1.5f) + ImGui::GetStyle().FramePadding.x * 2;
const float on_overhangs_only_width = m_imgui->calc_text_size(m_desc["on_overhangs_only"]).x + m_imgui->scaled(1.5f);
const float remove_btn_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.5f);
const float filter_btn_width = m_imgui->calc_text_size(m_desc.at("perform")).x + m_imgui->scaled(1.5f);
const float gap_area_txt_width = m_imgui->calc_text_size(m_desc.at("gap_area")).x + m_imgui->scaled(1.5f);
const float smart_fill_angle_txt_width = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.5f);
const float buttons_width = remove_btn_width + filter_btn_width + m_imgui->scaled(1.5f);
const float buttons_width = filter_btn_width + m_imgui->scaled(1.5f);
const float empty_button_width = m_imgui->calc_button_size("").x;
const float tips_width = m_imgui->calc_text_size(_L("Auto support threshold angle: ") + " 90 ").x + m_imgui->scaled(1.5f);
@@ -358,12 +360,27 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
ImGui::SameLine(drag_left_width + sliders_left_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##gap_area_input", &TriangleSelectorPatch::gap_area, 0.05f, 0.0f, 0.0f, "%.2f");
// Apply gap fill button
if (m_imgui->button(m_desc.at("perform"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset selection", UndoRedo::SnapshotType::GizmoAction);
for (int i = 0; i < m_triangle_selectors.size(); i++) {
TriangleSelectorPatch* ts_mm = dynamic_cast<TriangleSelectorPatch*>(m_triangle_selectors[i].get());
ts_mm->update_selector_triangles();
ts_mm->request_update_render_data(true);
}
update_model_object();
m_parent.set_as_dirty();
}
}
m_imgui->bbl_checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only);
if (ImGui::IsItemHovered())
m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]),
max_tooltip_width);
if (m_current_tool != ImGui::GapFillIcon) {
m_imgui->bbl_checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only);
if (ImGui::IsItemHovered())
m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]),
max_tooltip_width);
}
ImGui::Separator();
@@ -427,33 +444,12 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
if (b_bbl_slider_float || b_drag_input) m_c->object_clipper()->set_position_by_ratio(clp_dist, true);
ImGui::Separator();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f));
render_tooltip_button(x, y);
float f_scale =m_parent.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
ImGui::SameLine();
// Perform button is for gap fill
if (m_current_tool == ImGui::GapFillIcon) {
if (m_imgui->button(m_desc.at("perform"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset selection", UndoRedo::SnapshotType::GizmoAction);
for (int i = 0; i < m_triangle_selectors.size(); i++) {
TriangleSelectorPatch* ts_mm = dynamic_cast<TriangleSelectorPatch*>(m_triangle_selectors[i].get());
ts_mm->update_selector_triangles();
ts_mm->request_update_render_data(true);
}
update_model_object();
m_parent.set_as_dirty();
}
}
ImGui::SameLine();
m_imgui->disabled_begin(m_c->selection_info()->model_object()->is_fdm_support_painted() == false);
if (m_imgui->button(m_desc.at("remove_all"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset selection", UndoRedo::SnapshotType::GizmoAction);
ModelObject * mo = m_c->selection_info()->model_object();
@@ -468,11 +464,18 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
update_model_object();
m_parent.set_as_dirty();
}
ImGui::PopStyleVar(2);
m_imgui->disabled_end();
ImGui::SameLine();
GLGizmoUtils::begin_right_aligned_buttons({_L("Done")});
if (m_imgui->button(_L("Done"))) {
m_parent.reset_all_gizmos();
}
GizmoImguiEnd();
// BBS
ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding
ImGuiWrapper::pop_toolbar_style();
}
+14 -10
View File
@@ -37,7 +37,7 @@ bool GLGizmoFuzzySkin::on_init()
const wxString shift = GUI::shortkey_shift_prefix();
m_desc["reset_direction"] = _L("Reset direction");
m_desc["remove_all"] = _L("Erase all painting");
m_desc["remove_all"] = _L("Erase all");
m_desc["circle"] = _L("Circle");
m_desc["sphere"] = _L("Sphere");
m_desc["pointer"] = _L("Triangles");
@@ -137,6 +137,8 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim
// BBS
ImGuiWrapper::push_toolbar_style(m_parent.get_scale());
float f_scale = m_parent.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
@@ -151,7 +153,6 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim
const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f);
const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
const float buttons_width = m_imgui->scaled(0.5f);
const float minimal_slider_width = m_imgui->scaled(4.f);
@@ -176,7 +177,6 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim
const float empty_button_width = m_imgui->calc_button_size("").x;
window_width = std::max(window_width, total_text_max);
window_width = std::max(window_width, button_width);
window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer);
window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill);
window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f));
@@ -299,14 +299,10 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim
ImGui::Separator();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f));
render_tooltip_button(x, y);
float f_scale = m_parent.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
ImGui::SameLine();
m_imgui->disabled_begin(mo->is_fuzzy_skin_painted() == false);
if (m_imgui->button(m_desc.at("remove_all"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Reset selection"), UndoRedo::SnapshotType::GizmoAction);
int idx = -1;
@@ -320,6 +316,13 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim
update_model_object();
m_parent.set_as_dirty();
}
m_imgui->disabled_end();
ImGui::SameLine();
GLGizmoUtils::begin_right_aligned_buttons({_L("Done")});
if (m_imgui->button(_L("Done"))) {
m_parent.reset_all_gizmos();
}
const DynamicPrintConfig &glb_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
const bool has_object_fuzzy_override = obj_cfg.option("fuzzy_skin");
@@ -329,7 +332,7 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim
float font_size = ImGui::GetFontSize();
auto link_text = [&]() {
ImColor HyperColor = ImGuiWrapper::COL_ORCA;
ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::to_ImVec4(ColorRGB::WARNING()));
ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_WARNING);
float parent_width = ImGui::GetContentRegionAvail().x;
m_imgui->text_wrapped(_L("Warning: Fuzzy skin is disabled, painted fuzzy skin will not take effect!"), parent_width);
ImGui::PopStyleColor();
@@ -350,10 +353,11 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim
mo->config.assign_config(new_conf);
}
};
ImGui::Separator();
link_text();
}
ImGui::PopStyleVar(2);
ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding
GizmoImguiEnd();
// BBS
+11 -12
View File
@@ -1917,18 +1917,6 @@ void GLGizmoMeasure::show_selection_ui()
if (m_show_reset_first_tip) {
m_imgui->text(_L("Feature 1 has been reset, \nfeature 2 has been feature 1"));
}
if (m_selected_wrong_feature_waring_tip) {
if (m_measure_mode == EMeasureMode::ONLY_ASSEMBLY) {
if (m_assembly_mode == AssemblyMode::FACE_FACE) {
m_imgui->warning_text(_L("Warning: please select Plane's feature."));
} else if (m_assembly_mode == AssemblyMode::POINT_POINT) {
m_imgui->warning_text(_L("Warning: please select Point's or Circle's feature."));
}
}
}
if (m_measure_mode == EMeasureMode::ONLY_ASSEMBLY && m_hit_different_volumes.size() == 1) {
m_imgui->warning_text(_L("Warning: please select two different meshes."));
}
}
void GLGizmoMeasure::show_distance_xyz_ui()
@@ -2175,10 +2163,21 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit
ImGui::Separator();
show_distance_xyz_ui();
ImGui::Separator();
float f_scale = m_parent.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y);
ImGui::SameLine();
GLGizmoUtils::begin_right_aligned_buttons({ _L("Done") });
if (m_imgui->button(_L("Done"))) {
m_parent.reset_all_gizmos();
}
ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding
if (last_feature != m_curr_feature || last_mode != m_mode || last_selected_features != m_selected_features) {
// the dialog may have changed its size, ask for an extra frame to render it properly
last_feature = m_curr_feature;
+28 -4
View File
@@ -188,6 +188,19 @@ CommonGizmosDataID GLGizmoMeshBoolean::on_get_requirements() const
| int(CommonGizmosDataID::ObjectClipper));
}
std::optional<TriangleSelector::SavedPainting> VolumeInfo::save_painting() const
{
if (wxGetApp().app_config->get_bool("keep_painting")) {
std::optional<TriangleSelector::SavedPainting> saved_painting = mv->save_painting();
if (saved_painting) {
saved_painting->mesh.transform(trafo);
}
return saved_painting;
}
return {};
}
void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_limit)
{
y = std::min(y, bottom_limit - ImGui::GetWindowHeight());
@@ -346,7 +359,9 @@ void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_l
std::vector<TriangleMesh> temp_mesh_resuls;
Slic3r::MeshBoolean::mcut::make_boolean(temp_src_mesh, temp_tool_mesh, temp_mesh_resuls, "UNION");
if (temp_mesh_resuls.size() != 0) {
generate_new_volume(true, *temp_mesh_resuls.begin());
// For union, we want to keep paint from both meshes
std::vector<std::optional<TriangleSelector::SavedPainting>> saved_paintings{m_src.save_painting(), m_tool.save_painting()};
generate_new_volume(true, *temp_mesh_resuls.begin(), saved_paintings);
wxGetApp().notification_manager()->close_plater_warning_notification(warning_text);
}
else {
@@ -364,7 +379,9 @@ void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_l
std::vector<TriangleMesh> temp_mesh_resuls;
Slic3r::MeshBoolean::mcut::make_boolean(temp_src_mesh, temp_tool_mesh, temp_mesh_resuls, "A_NOT_B");
if (temp_mesh_resuls.size() != 0) {
generate_new_volume(m_diff_delete_input, *temp_mesh_resuls.begin());
// For diff, we only need paint from src
std::vector<std::optional<TriangleSelector::SavedPainting>> saved_paintings{m_src.save_painting()};
generate_new_volume(m_diff_delete_input, *temp_mesh_resuls.begin(), saved_paintings);
wxGetApp().notification_manager()->close_plater_warning_notification(warning_text);
}
else {
@@ -382,7 +399,9 @@ void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_l
std::vector<TriangleMesh> temp_mesh_resuls;
Slic3r::MeshBoolean::mcut::make_boolean(temp_src_mesh, temp_tool_mesh, temp_mesh_resuls, "INTERSECTION");
if (temp_mesh_resuls.size() != 0) {
generate_new_volume(m_inter_delete_input, *temp_mesh_resuls.begin());
// For intersection, we want to keep paint from both meshes
std::vector<std::optional<TriangleSelector::SavedPainting>> saved_paintings{m_src.save_painting(), m_tool.save_painting()};
generate_new_volume(m_inter_delete_input, *temp_mesh_resuls.begin(), saved_paintings);
wxGetApp().notification_manager()->close_plater_warning_notification(warning_text);
}
else {
@@ -420,7 +439,7 @@ void GLGizmoMeshBoolean::on_save(cereal::BinaryOutputArchive &ar) const
ar(m_enable, m_operation_mode, m_selecting_state, m_diff_delete_input, m_inter_delete_input, m_src, m_tool);
}
void GLGizmoMeshBoolean::generate_new_volume(bool delete_input, const TriangleMesh& mesh_result) {
void GLGizmoMeshBoolean::generate_new_volume(const bool delete_input, TriangleMesh& mesh_result, const std::vector<std::optional<TriangleSelector::SavedPainting>>& saved_paintings) {
wxGetApp().plater()->take_snapshot("Mesh Boolean");
@@ -429,6 +448,11 @@ void GLGizmoMeshBoolean::generate_new_volume(bool delete_input, const TriangleMe
// generate new volume
ModelVolume* new_volume = curr_model_object->add_volume(std::move(mesh_result));
// Remap paintings
for (const auto& saved_painting : saved_paintings) {
new_volume->restore_painting(saved_painting, true);
}
// assign to new_volume from old_volume
ModelVolume* old_volume = m_src.mv;
std::string suffix;
+3 -1
View File
@@ -34,6 +34,8 @@ struct VolumeInfo {
void serialize(Archive& ar) {
ar(volume_idx, trafo);
}
std::optional<TriangleSelector::SavedPainting> save_painting() const;
};
class GLGizmoMeshBoolean : public GLGizmoBase
{
@@ -87,7 +89,7 @@ private:
VolumeInfo m_src;
VolumeInfo m_tool;
void generate_new_volume(bool delete_input, const TriangleMesh& mesh_result);
void generate_new_volume(bool delete_input, TriangleMesh& mesh_result, const std::vector<std::optional<TriangleSelector::SavedPainting>>& saved_paintings);
};
} // namespace GUI
@@ -103,7 +103,7 @@ bool GLGizmoMmuSegmentation::on_init()
m_desc["edge_detection"] = _L("Edge detection");
m_desc["gap_area"] = _L("Gap area");
m_desc["perform"] = _L("Perform");
m_desc["remove_all"] = _L("Erase all painting");
m_desc["remove_all"] = _L("Erase all");
m_desc["circle"] = _L("Circle");
m_desc["sphere"] = _L("Sphere");
m_desc["pointer"] = _L("Triangles");
@@ -362,6 +362,9 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
// BBS
ImGuiWrapper::push_toolbar_style(m_parent.get_scale());
float f_scale = m_parent.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
@@ -374,10 +377,9 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
const float gap_area_slider_left = m_imgui->calc_text_size(m_desc.at("gap_area")).x + m_imgui->scaled(1.5f) + space_size;
const float height_range_slider_left = m_imgui->calc_text_size(m_desc.at("height_range")).x + m_imgui->scaled(2.f);
const float remove_btn_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
const float filter_btn_width = m_imgui->calc_text_size(m_desc.at("perform")).x + m_imgui->scaled(1.f);
const float remap_btn_width = m_imgui->calc_text_size(m_desc.at("perform_remap")).x + m_imgui->scaled(1.f);
const float buttons_width = remove_btn_width + filter_btn_width + remap_btn_width + m_imgui->scaled(2.f);
const float buttons_width = filter_btn_width + remap_btn_width + m_imgui->scaled(2.f);
const float minimal_slider_width = m_imgui->scaled(4.f);
const float color_button_width = m_imgui->calc_text_size(std::string_view{""}).x + m_imgui->scaled(1.75f);
@@ -566,8 +568,6 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
m_cursor_type = TriangleSelector::CursorType::POINTER;
m_tool_type = ToolType::BUCKET_FILL;
m_imgui->bbl_checkbox(m_desc["edge_detection"], m_detect_geometry_edge);
if (m_detect_geometry_edge) {
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc["smart_fill_angle"]);
@@ -587,6 +587,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
// set to negative value to disable edge detection
m_smart_fill_angle = -1.f;
}
m_imgui->bbl_checkbox(m_desc["edge_detection"], m_detect_geometry_edge);
}
else if (m_current_tool == ImGui::HeightRangeIcon) {
m_tool_type = ToolType::BRUSH;
@@ -613,6 +615,19 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
ImGui::SameLine(drag_left_width + sliders_left_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##gap_area_input", &TriangleSelectorPatch::gap_area, 0.05f, 0.0f, 0.0f, "%.2f");
// Apply Gap fill button
if (m_imgui->button(m_desc.at("perform"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Gap fill", UndoRedo::SnapshotType::GizmoAction);
for (int i = 0; i < m_triangle_selectors.size(); i++) {
TriangleSelectorPatch* ts_mm = dynamic_cast<TriangleSelectorPatch*>(m_triangle_selectors[i].get());
ts_mm->update_selector_triangles();
ts_mm->request_update_render_data(true);
}
update_model_object();
m_parent.set_as_dirty();
}
}
ImGui::Separator();
@@ -639,30 +654,10 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
ImGui::Separator();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f));
render_tooltip_button(x, y);
float f_scale =m_parent.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
ImGui::SameLine();
if (m_current_tool == ImGui::GapFillIcon) {
if (m_imgui->button(m_desc.at("perform"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Gap fill", UndoRedo::SnapshotType::GizmoAction);
for (int i = 0; i < m_triangle_selectors.size(); i++) {
TriangleSelectorPatch* ts_mm = dynamic_cast<TriangleSelectorPatch*>(m_triangle_selectors[i].get());
ts_mm->update_selector_triangles();
ts_mm->request_update_render_data(true);
}
update_model_object();
m_parent.set_as_dirty();
}
ImGui::SameLine();
}
m_imgui->disabled_begin(m_c->selection_info()->model_object()->is_mm_painted() == false);
if (m_imgui->button(m_desc.at("remove_all"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset selection", UndoRedo::SnapshotType::GizmoAction);
ModelObject * mo = m_c->selection_info()->model_object();
@@ -677,7 +672,15 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
update_model_object();
m_parent.set_as_dirty();
}
ImGui::PopStyleVar(2);
m_imgui->disabled_end();
ImGui::SameLine();
GLGizmoUtils::begin_right_aligned_buttons({_L("Done")});
if (m_imgui->button(_L("Done"))) {
m_parent.reset_all_gizmos();
}
ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding
GizmoImguiEnd();
// BBS
+13 -7
View File
@@ -41,7 +41,7 @@ bool GLGizmoSeam::on_init()
m_desc["enforce"] = _L("Enforce seam");
m_desc["block"] = _L("Block seam");
m_desc["remove"] = _L("Erase");
m_desc["remove_all"] = _L("Erase all painting");
m_desc["remove_all"] = _L("Erase all");
m_desc["circle"] = _L("Circle");
m_desc["sphere"] = _L("Sphere");
@@ -140,6 +140,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
wchar_t old_tool = m_current_tool;
//BBS
ImGuiWrapper::push_toolbar_style(m_parent.get_scale());
float f_scale = m_parent.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
@@ -256,14 +258,10 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
ImGui::Separator();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f));
GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y);
float f_scale =m_parent.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
ImGui::SameLine();
m_imgui->disabled_begin(m_c->selection_info()->model_object()->is_seam_painted() == false);
if (m_imgui->button(m_desc.at("remove_all"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset selection", UndoRedo::SnapshotType::GizmoAction);
ModelObject *mo = m_c->selection_info()->model_object();
@@ -278,7 +276,15 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
update_model_object();
m_parent.set_as_dirty();
}
ImGui::PopStyleVar(2);
m_imgui->disabled_end();
ImGui::SameLine();
GLGizmoUtils::begin_right_aligned_buttons({_L("Done")});
if (m_imgui->button(_L("Done"))) {
m_parent.reset_all_gizmos();
}
ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding
GizmoImguiEnd();
//BBS
+9 -1
View File
@@ -535,12 +535,20 @@ void GLGizmoSimplify::apply_simplify() {
auto plater = wxGetApp().plater();
plater->take_snapshot(GUI::format("Simplify %1%", m_volume->name));
plater->clear_before_change_mesh(object_idx);
const bool keep_painting = GUI::wxGetApp().app_config->get_bool("keep_painting");
if (!keep_painting) {
plater->clear_before_change_mesh(object_idx);
}
ModelVolume* mv = get_model_volume(selection, wxGetApp().model());
assert(mv == m_volume);
// Save paint
std::optional<TriangleSelector::SavedPainting> saved_painting = keep_painting ? mv->save_painting() :
std::optional<TriangleSelector::SavedPainting>{};
mv->set_mesh(std::move(*m_state.result));
// Remap paint
mv->restore_painting(saved_painting);
m_state.result.reset();
mv->calculate_convex_hull();
mv->invalidate_convex_hull_2d();
+87 -28
View File
@@ -26,6 +26,7 @@
- [Confirm], [Cancel], [Done], ... are buttons that close the Tool Dialog
- [Reset], [Button1], ... are buttons that do not!
- Non-consequential buttons like [Cancel] and [Done] are always the right-most buttons
- [Confirm] buttons should use the orca_button_style to differentiate them from other buttons
- Multiple warnings can show, but should only have one ImGui::Separator above
- If no warnings is shown, dont render the ImGui::Separator
@@ -33,43 +34,101 @@
namespace Slic3r::GUI::GLGizmoUtils {
void render_tooltip_button(
ImGuiWrapper* imgui_wrapper, const GLCanvas3D& canvas, const std::vector<std::pair<wxString, wxString>>& shortcuts, float x, float y)
{
float caption_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y;
float caption_x_max = 0.f;
for (const auto& item : shortcuts) {
caption_x_max = std::max(caption_x_max, imgui_wrapper->calc_text_size(item.first).x);
}
caption_x_max += imgui_wrapper->calc_text_size(": "sv).x + 35.f;
void render_tooltip_button(
ImGuiWrapper* imgui_wrapper, const GLCanvas3D& canvas, const std::vector<std::pair<wxString, wxString>>& shortcuts, float x, float y)
{
float caption_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y;
float caption_x_max = 0.f;
for (const auto& item : shortcuts) {
caption_x_max = std::max(caption_x_max, imgui_wrapper->calc_text_size(item.first).x);
}
caption_x_max += imgui_wrapper->calc_text_size(": "sv).x + 35.f;
auto& gizmos_manager = canvas.get_gizmos_manager();
ImTextureID normal_id = gizmos_manager.get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP);
ImTextureID hover_id = gizmos_manager.get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER);
auto& gizmos_manager = canvas.get_gizmos_manager();
ImTextureID normal_id = gizmos_manager.get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP);
ImTextureID hover_id = gizmos_manager.get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER);
float scale = canvas.get_scale();
float scale = canvas.get_scale();
#ifdef WIN32
int dpi = get_dpi_for_window(wxGetApp().GetTopWindow());
scale *= (float) dpi / (float) DPI_DEFAULT;
int dpi = get_dpi_for_window(wxGetApp().GetTopWindow());
scale *= (float)dpi / (float)DPI_DEFAULT;
#endif
ImVec2 button_size = ImVec2(25 * scale, 25 * scale);
ImVec2 button_size = ImVec2(25 * scale, 25 * scale);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, 0});
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 0, 0 });
ImGui::ImageButton3(normal_id, hover_id, button_size);
ImGui::ImageButton3(normal_id, hover_id, button_size);
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip2(ImVec2(x, caption_y));
for (const auto& item : shortcuts) {
imgui_wrapper->text_colored(ImGuiWrapper::COL_ACTIVE, item.first + ": ");
ImGui::SameLine(caption_x_max);
imgui_wrapper->text_colored(ImGuiWrapper::COL_WINDOW_BG, item.second);
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip2(ImVec2(x, caption_y));
for (const auto& item : shortcuts) {
imgui_wrapper->text_colored(ImGuiWrapper::COL_ACTIVE, item.first + ": ");
ImGui::SameLine(caption_x_max);
imgui_wrapper->text_colored(ImGuiWrapper::COL_WINDOW_BG, item.second);
}
ImGui::EndTooltip();
}
ImGui::EndTooltip();
ImGui::PopStyleVar(2);
}
void begin_right_aligned_buttons(const std::vector<wxString>& labels)
{
float total_width = 0.0f;
ImGuiStyle& style = ImGui::GetStyle();
float spacing = style.ItemSpacing.x;
float padding = style.FramePadding.x * 2.0f;
// Calculate width
for (size_t i = 0; i < labels.size(); ++i) {
total_width += ImGuiWrapper::calc_text_size(labels[i]).x + padding;
if (i < labels.size() - 1)
total_width += spacing;
}
float avail = ImGui::GetContentRegionAvail().x;
// Handle Overlap: If the total width of the buttons exceeds available space, move to a new line
if (total_width > avail) {
ImGui::NewLine();
avail = ImGui::GetContentRegionAvail().x; // Reset to full window width
}
float posX = ImGui::GetCursorPosX() + std::max(0.0f, avail - total_width);
ImGui::SetCursorPosX(posX);
}
void push_orca_button_style()
{
ImVec4 base_orca = ImGuiWrapper::COL_ORCA;
float h, s, v;
ImGui::ColorConvertRGBtoHSV(base_orca.x, base_orca.y, base_orca.z, h, s, v);
ImVec4 hover, active;
// Lighter variant for Hover (Increase Value by ~12%)
ImGui::ColorConvertHSVtoRGB(h, s, std::min(v + 0.12f, 1.0f), hover.x, hover.y, hover.z);
hover.w = base_orca.w;
// Darker variant for Active (Decrease Value by ~12%)
ImGui::ColorConvertHSVtoRGB(h, s, std::max(v - 0.12f, 0.0f), active.x, active.y, active.z);
active.w = base_orca.w;
ImGui::PushStyleColor(ImGuiCol_Button, base_orca);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, hover);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, active);
ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_WINDOW_BG);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
}
void pop_orca_button_style()
{
ImGui::PopStyleVar(1);
ImGui::PopStyleColor(4);
}
ImGui::PopStyleVar(2);
}
} // namespace Slic3r::GUI::GLGizmoUtils
+10 -3
View File
@@ -15,9 +15,16 @@ class GLCanvas3D;
namespace GLGizmoUtils {
// Renders a tooltip button using the provided shortcuts
void render_tooltip_button(
ImGuiWrapper* imgui_wrapper, const GLCanvas3D& canvas, const std::vector<std::pair<wxString, wxString>>& shortcuts, float x, float y);
// Renders a tooltip button using the provided shortcuts
void render_tooltip_button(
ImGuiWrapper* imgui_wrapper, const GLCanvas3D& canvas, const std::vector<std::pair<wxString, wxString>>& shortcuts, float x, float y);
// Sets up ImGui to render buttons that are right-aligned within the current window, using the provided labels to calculate spacing.
void begin_right_aligned_buttons(const std::vector<wxString>& labels);
void push_orca_button_style();
void pop_orca_button_style();
} // namespace GLGizmoUtils
} // namespace Slic3r::GUI
@@ -62,16 +62,21 @@ GizmoObjectManipulation::GizmoObjectManipulation(GLCanvas3D& glcanvas)
m_shortcuts_move = {
{alt + _L("Left mouse button"), _L("Part selection")},
{shift + _L("Left mouse button"), _L("Fixed step drag")}
{shift + _L("Left mouse button"), _L("Fixed step drag")},
{_L("Context Menu"), _L("Toggle Auto-Drop")}
};
m_shortcuts_rotate = {
{alt + _L("Left mouse button"), _L("Part selection")}};
{alt + _L("Left mouse button"), _L("Part selection")},
{_L("Context Menu"), _L("Toggle Auto-Drop")}
};
m_shortcuts_scale = {
{alt + _L("Left mouse button"), _L("Part selection")},
{shift + _L("Left mouse button"), _L("Fixed step drag")},
{ctrl + _L("Left mouse button"), _L("Single sided scaling")}};
{ctrl + _L("Left mouse button"), _L("Single sided scaling")},
{_L("Context Menu"), _L("Toggle Auto-Drop")}
};
}
void GizmoObjectManipulation::UpdateAndShow(const bool show)
@@ -860,13 +865,25 @@ void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper,
}
}
if (!focued_on_text) m_glcanvas.handle_sidebar_focus_event("", false);
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
float f_scale = m_glcanvas.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
GLGizmoUtils::render_tooltip_button(imgui_wrapper, m_glcanvas, m_shortcuts_move, x, y);
ImGui::SameLine();
GLGizmoUtils::begin_right_aligned_buttons({ _L("Done") });
if (imgui_wrapper->button(_L("Done"))) {
m_glcanvas.reset_all_gizmos();
}
m_last_active_item = current_active_id;
last_move_input_window_width = ImGui::GetWindowWidth();
imgui_wrapper->end();
ImGui::PopStyleVar(1);
ImGui::PopStyleVar(2);
ImGuiWrapper::pop_toolbar_style();
}
@@ -1052,14 +1069,26 @@ void GizmoObjectManipulation::do_render_rotate_window(ImGuiWrapper *imgui_wrappe
if (!focued_on_text && !absolute_focued_on_text)
m_glcanvas.handle_sidebar_focus_event("", false);
ImGui::Spacing(); // needed after Text
ImGui::Separator();
ImGui::Spacing();
float f_scale = m_glcanvas.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
GLGizmoUtils::render_tooltip_button(imgui_wrapper, m_glcanvas, m_shortcuts_rotate, x, y);
ImGui::SameLine();
GLGizmoUtils::begin_right_aligned_buttons({ _L("Done") });
if (imgui_wrapper->button(_L("Done"))) {
m_glcanvas.reset_all_gizmos();
}
m_last_active_item = current_active_id;
last_rotate_input_window_width = ImGui::GetWindowWidth();
imgui_wrapper->end();
// BBS
ImGui::PopStyleVar(1);
ImGui::PopStyleVar(2);
ImGuiWrapper::pop_toolbar_style();
}
@@ -1214,7 +1243,7 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w
if (display_size.x() > 0 && display_size.y() > 0 && display_size.z() > 0) {
m_buffered_size = display_size;
}
ImGui::Separator();
ImGui::AlignTextToFramePadding();
bool is_avoid_one_update{false};
if (combox_changed) {
@@ -1286,15 +1315,26 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w
}
if (!focued_on_text)
m_glcanvas.handle_sidebar_focus_event("", false);
ImGui::Separator();
float f_scale = m_glcanvas.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
GLGizmoUtils::render_tooltip_button(imgui_wrapper, m_glcanvas, m_shortcuts_scale, x, y);
ImGui::SameLine();
GLGizmoUtils::begin_right_aligned_buttons({ _L("Done") });
if (imgui_wrapper->button(_L("Done"))) {
m_glcanvas.reset_all_gizmos();
}
m_last_active_item = current_active_id;
last_scale_input_window_width = ImGui::GetWindowWidth();
imgui_wrapper->end();
//BBS
ImGui::PopStyleVar(1);
ImGuiWrapper::pop_toolbar_style();
}
+6 -5
View File
@@ -185,6 +185,7 @@ const ImVec4 ImGuiWrapper::COL_TOOLBAR_BG = { 250 / 255.f, 250 / 255.f, 2
const ImVec4 ImGuiWrapper::COL_TOOLBAR_BG_DARK = { 57 / 255.f, 60 / 255.f, 66 / 255.f, 1.f }; // ORCA color matches with toolbar_background_dark.png
const ImVec4 ImGuiWrapper::COL_ORCA = to_ImVec4(ColorRGBA::ORCA());
const ImVec4 ImGuiWrapper::COL_MODIFIED = { 253.f / 255.f, 111.f / 255.f, 40.f / 255.f, 1}; // ORCA same color with m_color_label_modified
const ImVec4 ImGuiWrapper::COL_WARNING = to_ImVec4(ColorRGB::WARNING());
int ImGuiWrapper::TOOLBAR_WINDOW_FLAGS = ImGuiWindowFlags_AlwaysAutoResize
| ImGuiWindowFlags_NoMove
@@ -871,8 +872,8 @@ bool ImGuiWrapper::button(const wxString &label, const wxString& tooltip)
const bool ret = ImGui::Button(label_utf8.c_str());
if (!tooltip.IsEmpty() && ImGui::IsItemHovered()) {
auto tooltip_utf8 = into_u8(tooltip);
ImGui::SetTooltip(tooltip_utf8.c_str(), nullptr);
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
this->tooltip(tooltip, max_tooltip_width);
}
return ret;
@@ -884,8 +885,8 @@ bool ImGuiWrapper::bbl_button(const wxString &label, const wxString& tooltip)
const bool ret = ImGui::BBLButton(label_utf8.c_str());
if (!tooltip.IsEmpty() && ImGui::IsItemHovered()) {
auto tooltip_utf8 = into_u8(tooltip);
ImGui::SetTooltip(tooltip_utf8.c_str(), nullptr);
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
this->tooltip(tooltip, max_tooltip_width);
}
return ret;
@@ -1055,7 +1056,7 @@ void ImGuiWrapper::text(const wxString &label)
void ImGuiWrapper::warning_text(const char *label)
{
ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::to_ImVec4(ColorRGB::WARNING()));
ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_WARNING);
this->text(label);
ImGui::PopStyleColor();
}
+3 -1
View File
@@ -349,6 +349,7 @@ public:
static const ImVec4 COL_SEPARATOR_DARK;
static const ImVec4 COL_ORCA;
static const ImVec4 COL_MODIFIED;
static const ImVec4 COL_WARNING;
//BBS
static void on_change_color_mode(bool is_dark);
@@ -372,12 +373,13 @@ public:
//BBS
static int TOOLBAR_WINDOW_FLAGS;
bool display_initialized() const;
private:
void init_font(bool compress);
void init_input();
void init_style();
void render_draw_data(ImDrawData *draw_data);
bool display_initialized() const;
void destroy_font();
std::vector<unsigned char> load_svg(const std::string& bitmap_name, unsigned target_width, unsigned target_height, unsigned *outwidth, unsigned *outheight);
+10 -4
View File
@@ -207,13 +207,19 @@ void KBShortcutsDialog::fill_shortcuts()
{ "?", L("Show keyboard shortcuts list") }
};
m_full_shortcuts.push_back({{_L("Global shortcuts"), ""}, global_shortcuts});
bool swap_mouse_buttons = wxGetApp().app_config->get_bool("swap_mouse_buttons");
// Retrieve mouse actions from config and map to MouseAction
std::map<std::string, std::string> mouse_actions;
mouse_actions["0"] = L("None");
mouse_actions["1"] = L("Pan View");
mouse_actions["2"] = L("Rotate View");
Shortcuts plater_shortcuts = {
{ L("Left mouse button"), swap_mouse_buttons ? L("Pan view") : L("Rotate view") },
{ L("Right mouse button"), swap_mouse_buttons ? L("Rotate view") : L("Pan view") },
{ L("Mouse wheel"), L("Zoom view") },
{ L("Left mouse button"), mouse_actions[wxGetApp().app_config->get("left_mouse_drag_action").c_str()]},
{ L("Middle mouse button"), mouse_actions[wxGetApp().app_config->get("middle_mouse_drag_action").c_str()]},
{ L("Right mouse button"), mouse_actions[wxGetApp().app_config->get("right_mouse_drag_action").c_str()]},
{ L("Mouse wheel"), L("Zoom View") },
{ "A", L("Arrange all objects") },
{ shift + "A", L("Arrange objects on selected plates") },
+34
View File
@@ -2780,6 +2780,21 @@ void MainFrame::init_menubar_as_editor()
append_submenu(fileMenu, export_menu, wxID_ANY, _L("Export"), "");
fileMenu->AppendSeparator();
append_menu_item(fileMenu, wxID_ANY, _L("Sync Presets"), _L("Pull and apply the latest presets from OrcaCloud"),
[this](wxCommandEvent&) {
if (!wxGetApp().is_user_login()) {
MessageDialog info_dlg(this, _L("You must be logged in to sync presets from cloud."),
_L("Sync Presets"), wxOK | wxICON_INFORMATION);
info_dlg.ShowModal();
return;
}
wxGetApp().restart_sync_user_preset();
}, "", nullptr,
[this]() {
return wxGetApp().is_user_login() && !wxGetApp().app_config->get_stealth_mode();
}, this);
fileMenu->AppendSeparator();
#ifndef __APPLE__
append_menu_item(fileMenu, wxID_EXIT, _L("Quit"), wxString::Format(_L("Quit")),
@@ -3277,6 +3292,25 @@ void MainFrame::init_menubar_as_editor()
plater()->get_current_canvas3D()->force_set_focus();
},
"", nullptr, []() { return true; }, this);
append_menu_item(
m_topbar->GetTopMenu(), wxID_ANY, _L("Sync Presets"), _L("Pull and apply the latest presets from OrcaCloud"),
[this](wxCommandEvent&) {
if (!wxGetApp().is_user_login()) {
MessageDialog info_dlg(this, _L("You must be logged in to sync presets from cloud."),
_L("Sync Presets"), wxOK | wxICON_INFORMATION);
info_dlg.ShowModal();
return;
}
if (m_plater)
m_plater->get_notification_manager()->push_notification(
into_u8(_L("Syncing presets from cloud\u2026")));
wxGetApp().restart_sync_user_preset();
}, "", nullptr,
[this]() {
return wxGetApp().is_user_login() && !wxGetApp().app_config->get_stealth_mode();
}, this);
//m_topbar->AddDropDownMenuItem(preference_item);
//m_topbar->AddDropDownMenuItem(printer_item);
//m_topbar->AddDropDownMenuItem(language_item);
+4 -4
View File
@@ -184,7 +184,7 @@ void MediaPlayCtrl::SetMachineObject(MachineObject* obj)
&& m_last_user_play + wxTimeSpan::Seconds(3) < wxDateTime::Now()) {
// resend ttcode to printer
if (auto agent = wxGetApp().getAgent())
agent->get_camera_url(machine, [](auto) {});
agent->get_camera_url(machine, [](auto) {}, wxGetApp().get_printer_cloud_provider());
m_last_user_play = wxDateTime::Now();
}
return;
@@ -252,7 +252,7 @@ void refresh_agora_url(char const* device, char const* dev_ver, char const* chan
device2 += channel;
wxGetApp().getAgent()->get_camera_url(device2, [context, callback](std::string url) {
callback(context, url.c_str());
});
}, wxGetApp().get_printer_cloud_provider());
}
void MediaPlayCtrl::Play()
@@ -378,7 +378,7 @@ void MediaPlayCtrl::Play()
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl drop late ttcode for state: " << m_last_state;
}
});
});
}, wxGetApp().get_printer_cloud_provider());
}
}
@@ -580,7 +580,7 @@ void MediaPlayCtrl::ToggleStream()
file.close();
m_streaming = true;
});
});
}, wxGetApp().get_printer_cloud_provider());
}
void MediaPlayCtrl::msw_rescale() {
+58
View File
@@ -0,0 +1,58 @@
#include "MultiChoiceDialog.hpp"
#include "GUI_App.hpp"
#include "MainFrame.hpp"
namespace Slic3r { namespace GUI {
MultiChoiceDialog::MultiChoiceDialog(
wxWindow* parent,
const wxString& message,
const wxString& caption,
const wxArrayString& choices
)
: DPIDialog(parent ? parent : static_cast<wxWindow *>(wxGetApp().mainframe), wxID_ANY, caption, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
{
SetBackgroundColour(*wxWHITE);
wxBoxSizer* w_sizer = new wxBoxSizer(wxVERTICAL);
if(!message.IsEmpty()){
wxStaticText *msg = new wxStaticText(this, wxID_ANY, message);
msg->SetFont(Label::Body_13);
msg->Wrap(-1);
w_sizer->Add(msg, 0, wxRIGHT | wxLEFT | wxTOP, FromDIP(10));
}
m_check_list = new CheckList(this, choices);
w_sizer->Add(m_check_list, 1, wxRIGHT | wxLEFT | wxTOP | wxEXPAND, FromDIP(10));
auto dlg_btns = new DialogButtons(this, {"OK", "Cancel"});
dlg_btns->GetOK()->Bind( wxEVT_BUTTON, [this](wxCommandEvent &e) {EndModal(wxID_OK);});
dlg_btns->GetCANCEL()->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) {EndModal(wxID_CANCEL);});
w_sizer->Add(dlg_btns, 0, wxEXPAND);
SetSizer(w_sizer);
Layout();
w_sizer->Fit(this);
wxGetApp().UpdateDlgDarkUI(this);
}
wxArrayInt MultiChoiceDialog::GetSelections() const
{
return m_check_list->GetSelections();
}
void MultiChoiceDialog::SetSelections(wxArrayInt sel_array)
{
m_check_list->SetSelections(sel_array);
}
MultiChoiceDialog::~MultiChoiceDialog() {}
void MultiChoiceDialog::on_dpi_changed(const wxRect &suggested_rect) {}
}} // namespace Slic3r::GUI
+37
View File
@@ -0,0 +1,37 @@
#ifndef slic3r_GUI_MultiChoiceDialog_hpp_
#define slic3r_GUI_MultiChoiceDialog_hpp_
#include "Widgets/CheckList.hpp"
#include "Widgets/DialogButtons.hpp"
#include <wx/wx.h>
#include <vector>
#include <map>
namespace Slic3r { namespace GUI {
class MultiChoiceDialog : public DPIDialog
{
public:
MultiChoiceDialog(
wxWindow* parent = nullptr,
const wxString& message = wxEmptyString,
const wxString& caption = wxEmptyString,
const wxArrayString& choices = wxArrayString()
);
~MultiChoiceDialog();
wxArrayInt GetSelections() const;
void SetSelections(wxArrayInt sel_array);
protected:
CheckList* m_check_list;
wxArrayInt m_selected_indices;
void on_dpi_changed(const wxRect &suggested_rect) override;
};
}} // namespace Slic3r::GUI
#endif
+4
View File
@@ -332,12 +332,16 @@ bool ObjectDataViewModelNode::SetValue(const wxVariant& variant, unsigned col)
m_variable_height_icon << variant;
return true;
case colName: {
if (variant.GetType() != wxT("DataViewBitmapText"))
return false;
DataViewBitmapText data;
data << variant;
m_bmp = data.GetBitmap();
m_name = data.GetText();
return true; }
case colFilament: {
if (variant.GetType() != wxT("DataViewBitmapText"))
return false;
DataViewBitmapText data;
data << variant;
m_extruder_bmp = data.GetBitmap();
+20 -5
View File
@@ -5,6 +5,7 @@
#include "I18N.hpp"
#include "3DScene.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Platform.hpp"
#include <glad/gl.h>
@@ -408,6 +409,13 @@ wxGLContext* OpenGLManager::init_glcontext(wxGLCanvas& canvas, const std::pair<i
wxGLCanvas* OpenGLManager::create_wxglcanvas(wxWindow& parent)
{
int antialiasing_samples = 4;
if (const AppConfig* app_config = get_app_config(); app_config != nullptr) {
const std::string value = app_config->get(SETTING_OPENGL_AA_SAMPLES);
if (value == "0" || value == "2" || value == "4" || value == "8" || value == "16")
antialiasing_samples = ::atoi(value.c_str());
}
int attribList[] = {
WX_GL_RGBA,
WX_GL_DOUBLEBUFFER,
@@ -421,19 +429,26 @@ wxGLCanvas* OpenGLManager::create_wxglcanvas(wxWindow& parent)
WX_GL_DEPTH_SIZE, 24,
//BBS: turn on stencil buffer for outline
WX_GL_STENCIL_SIZE, 8,
WX_GL_SAMPLE_BUFFERS, GL_TRUE,
WX_GL_SAMPLES, 4,
WX_GL_SAMPLE_BUFFERS, antialiasing_samples > 0 ? GL_TRUE : GL_FALSE,
WX_GL_SAMPLES, antialiasing_samples,
0
};
if (s_multisample == EMultisampleState::Unknown) {
constexpr int sample_buffers_value_idx = 15;
constexpr int samples_value_idx = 17;
if (antialiasing_samples <= 0)
s_multisample = EMultisampleState::Disabled;
else if (s_multisample == EMultisampleState::Unknown) {
detect_multisample(attribList);
// // debug output
// std::cout << "Multisample " << (can_multisample() ? "enabled" : "disabled") << std::endl;
}
if (! can_multisample())
attribList[12] = 0;
if (! can_multisample()) {
attribList[sample_buffers_value_idx] = GL_FALSE;
attribList[samples_value_idx] = 0;
}
wxGLCanvas* canvas = new wxGLCanvas(&parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS);
// The GL canvas paints its entire surface, so background erasing is unnecessary.
+15 -3
View File
@@ -279,9 +279,12 @@ ParamsPanel::ParamsPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, c
});
m_mode_icon->SetToolTip(_L("Cycle settings visibility"));
m_mode_view = new ModeSwitchButton(m_top_panel);
m_mode_view->SetSelection(mode_to_selection(wxGetApp().get_saved_mode()));
if (wxGetApp().get_mode() == comDevelop)
if (wxGetApp().get_mode() == comDevelop) {
m_mode_view->SetSelection(mode_to_selection(comExpert));
m_mode_view->Enable(false);
} else {
m_mode_view->SetSelection(mode_to_selection(wxGetApp().get_saved_mode()));
}
// BBS: new layout
//m_search_btn = new ScalableButton(m_top_panel, wxID_ANY, "search", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true);
@@ -648,18 +651,27 @@ void ParamsPanel::update_mode()
if (mode_view == nullptr)
return;
mode_view->SetSelection(mode_to_selection(Slic3r::GUI::wxGetApp().get_saved_mode()));
if (app_mode == comDevelop) {
mode_view->SetSelection(mode_to_selection(comExpert));
mode_view->Enable(false);
return;
}
mode_view->SetSelection(mode_to_selection(Slic3r::GUI::wxGetApp().get_saved_mode()));
if (!mode_view->IsEnabled())
mode_view->Enable();
};
sync_mode_view(m_mode_view);
sync_mode_view(m_current_tab ? dynamic_cast<Tab*>(m_current_tab)->m_mode_view : nullptr);
auto sync_mode_icon = [&](ScalableButton* mode_icon) {
if (mode_icon == nullptr)
return;
mode_icon->Show(app_mode != comDevelop);
};
sync_mode_icon(m_mode_icon);
sync_mode_icon(m_current_tab ? dynamic_cast<Tab*>(m_current_tab)->m_mode_icon : nullptr);
}
void ParamsPanel::msw_rescale()
+2 -2
View File
@@ -824,7 +824,7 @@ bool PartSkipDialog::IsAllChecked()
return true;
}
bool PartSkipDialog::IsAllCancled()
bool PartSkipDialog::IsAllCanceled()
{
for (auto &[part_id, part_state] : m_parts_state) {
if (part_state == PartState::psChecked) return false;
@@ -851,7 +851,7 @@ void PartSkipDialog::OnAllCheckbox(wxCommandEvent &event)
void PartSkipDialog::UpdateApplyButtonStatus()
{
if (IsAllCancled()) {
if (IsAllCanceled()) {
m_apply_btn->SetStyle(ButtonStyle::Regular, ButtonType::Choice);
m_apply_btn->SetToolTip(_L("Nothing selected"));
m_enable_apply_btn = false;
+1 -1
View File
@@ -153,7 +153,7 @@ private:
void UpdateDialogUI();
void UpdateApplyButtonStatus();
bool IsAllChecked();
bool IsAllCancled();
bool IsAllCanceled();
void OnRetryButton(wxCommandEvent &event);
void OnAllCheckbox(wxCommandEvent &event);
+76 -44
View File
@@ -2406,13 +2406,23 @@ void Sidebar::init_filament_combo(PlaterPresetComboBox **combo, const int filame
edit_btn->SetToolTip(_L("Click to edit preset"));
PlaterPresetComboBox* combobox = (*combo);
edit_btn->Bind(wxEVT_BUTTON, [this, edit_btn, filament_idx](wxCommandEvent) {
auto menu = p->plater->filament_action_menu(filament_idx);
wxPoint pt { 0, edit_btn->GetSize().GetHeight() + 10 };
pt = edit_btn->ClientToScreen(pt);
pt = wxGetApp().mainframe->ScreenToClient(pt);
p->m_menu_filament_id = filament_idx;
p->plater->PopupMenu(menu, (int) pt.x, pt.y);
edit_btn->Bind(wxEVT_BUTTON, [this, edit_btn, combobox, filament_idx](wxCommandEvent) {
bool single_or_bbl = should_show_SEMM_buttons();
bool is_multi_material = p->combos_filament.size() > 1;
if(single_or_bbl && is_multi_material) {
// MULTI MATERIAL Show menu
auto menu = p->plater->filament_action_menu(filament_idx);
wxPoint pt { 0, edit_btn->GetSize().GetHeight() + FromDIP(2) };
pt = edit_btn->ClientToScreen(pt);
pt = wxGetApp().mainframe->ScreenToClient(pt);
p->m_menu_filament_id = filament_idx;
p->plater->PopupMenu(menu, (int) pt.x, pt.y);
}
else {
// SINGLE MATERIAL / MULTI EXTRUDER / TOOLCHANGER / IDEX Opens Dialog directly
p->editing_filament = filament_idx;
combobox->switch_to_tab();
}
});
combobox->edit_btn = edit_btn;
@@ -2513,7 +2523,7 @@ void Sidebar::update_all_preset_comboboxes()
p->m_filament_icon->SetBitmap_("filament");
}
show_SEMM_buttons(should_show_SEMM_buttons());
show_SEMM_buttons();
//p->m_staticText_filament_settings->Update();
@@ -3115,16 +3125,7 @@ void Sidebar::on_filament_count_change(size_t num_filaments)
// remove unused choices if any
remove_unused_filament_combos(num_filaments);
auto sizer = p->m_panel_filament_title->GetSizer();
if (p->m_flushing_volume_btn != nullptr && sizer != nullptr) {
if (num_filaments > 1) {
sizer->Show(p->m_flushing_volume_btn);
sizer->Show(p->m_bpButton_del_filament); // ORCA: Show delete filament button if multiple filaments
} else {
sizer->Hide(p->m_flushing_volume_btn);
sizer->Hide(p->m_bpButton_del_filament); // ORCA: Hide delete filament button if there is only one filament
}
}
show_SEMM_buttons(); // ORCA
update_filaments_area_height(); // ORCA
@@ -3171,16 +3172,7 @@ void Sidebar::on_filaments_delete(size_t filament_id)
}
}
auto sizer = p->m_panel_filament_title->GetSizer();
if (p->m_flushing_volume_btn != nullptr && sizer != nullptr) {
if (p->combos_filament.size() > 1) {
sizer->Show(p->m_flushing_volume_btn);
sizer->Show(p->m_bpButton_del_filament); // ORCA: Show delete filament button if multiple filaments
} else {
sizer->Hide(p->m_flushing_volume_btn);
sizer->Hide(p->m_bpButton_del_filament); // ORCA: Hide delete filament button if there is only one filament
}
}
show_SEMM_buttons(); // ORCA
for (size_t idx = filament_id ; idx < p->combos_filament.size(); ++idx) {
p->combos_filament[idx]->update();
@@ -3739,14 +3731,31 @@ bool Sidebar::should_show_SEMM_buttons()
return cfg.opt_bool("single_extruder_multi_material") || is_bbl_vendor;
}
void Sidebar::show_SEMM_buttons(bool bshow)
void Sidebar::show_SEMM_buttons()
{
if(p->m_bpButton_add_filament)
p->m_bpButton_add_filament->Show(bshow);
if (p->m_bpButton_del_filament && p->combos_filament.size() > 1) // ORCA add filament count as condition to prevent showing Flushing volumes and Del Filament icon visible while only 1 filament exist
p->m_bpButton_del_filament->Show(bshow);
if (p->m_flushing_volume_btn && p->combos_filament.size() > 1) // ORCA add filament count as condition to prevent showing Flushing volumes and Del Filament icon visible while only 1 filament exist
p->m_flushing_volume_btn->Show(bshow);
// ORCA
if (!p || p->combos_filament.empty() || !p->m_bpButton_add_filament || !p->m_bpButton_del_filament || !p->m_flushing_volume_btn)
return;
bool is_multi_material = p->combos_filament.size() > 1;
bool single_or_bbl = should_show_SEMM_buttons();
bool is_single = single_or_bbl && !is_multi_material; // SINGLE EXTRUDER / BBL WITH 1 MATERIAL
bool is_multi = single_or_bbl && is_multi_material; // MULTI MATERIAL WITH SINGLE EXTRUDER
bool is_fixed = !is_single && !is_multi; // MULTI EXTRUDER / TOOLCHANGER / IDEX WITH FIXED MATERIAL
p->m_bpButton_add_filament->Show(single_or_bbl);
p->m_bpButton_del_filament->Show(is_multi);
p->m_flushing_volume_btn->Show( is_multi);
if (is_multi) {
for (auto &c : p->combos_filament)
c->edit_btn->SetBitmap_("menu_filament");
}
else if (is_single || is_fixed) {
for (auto &c : p->combos_filament)
c->edit_btn->SetBitmap_("edit");
}
Layout();
}
@@ -7631,7 +7640,7 @@ void Plater::priv::split_object(int obj_idx, bool auto_drop /* = true */)
wxBusyCursor wait;
ModelObjectPtrs new_objects;
current_model_object->split(&new_objects);
current_model_object->split(&new_objects, wxGetApp().app_config->get_bool("keep_painting"));
if (new_objects.size() == 1)
// #ysFIXME use notification
Slic3r::GUI::warning_catcher(q, _L("The selected object couldn't be split."));
@@ -8395,10 +8404,20 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const
new_volume->convert_from_imperial_units();
else if (old_volume->source.is_converted_from_meters)
new_volume->convert_from_meters();
new_volume->supported_facets.assign(old_volume->supported_facets);
new_volume->seam_facets.assign(old_volume->seam_facets);
new_volume->mmu_segmentation_facets.assign(old_volume->mmu_segmentation_facets);
new_volume->fuzzy_skin_facets.assign(old_volume->fuzzy_skin_facets);
if (wxGetApp().app_config->get_bool("keep_painting")) {
// Proper paint remapping
auto saved_painting = old_volume->save_painting();
if (saved_painting) {
saved_painting->mesh.transform(Geometry::translation_transform(new_volume->mesh().get_init_shift()));
new_volume->restore_painting(saved_painting);
}
} else {
// Won't work well if mesh changed, but kept for old behavior
new_volume->supported_facets.assign(old_volume->supported_facets);
new_volume->seam_facets.assign(old_volume->seam_facets);
new_volume->mmu_segmentation_facets.assign(old_volume->mmu_segmentation_facets);
new_volume->fuzzy_skin_facets.assign(old_volume->fuzzy_skin_facets);
}
std::swap(old_model_object->volumes[volume_idx], old_model_object->volumes.back());
old_model_object->delete_volume(old_model_object->volumes.size() - 1);
if (!sinking)
@@ -8924,6 +8943,16 @@ void Plater::priv::reload_from_disk()
new_volume->convert_from_imperial_units();
else if (old_volume->source.is_converted_from_meters)
new_volume->convert_from_meters();
// Remap paint
if (wxGetApp().app_config->get_bool("keep_painting")) {
auto saved_painting = old_volume->save_painting();
if (saved_painting) {
saved_painting->mesh.transform(Geometry::translation_transform(new_volume->mesh().get_init_shift()));
new_volume->restore_painting(saved_painting);
}
}
std::swap(old_model_object->volumes[vol_idx], old_model_object->volumes.back());
old_model_object->delete_volume(old_model_object->volumes.size() - 1);
if (!sinking) old_model_object->ensure_on_bed();
@@ -15026,7 +15055,8 @@ void Plater::export_gcode(bool prefer_removable)
unsigned int state = this->p->update_restart_background_process(false, false);
if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID)
return;
default_output_file = this->p->background_process.output_filepath_for_project("");
default_output_file = this->p->background_process.output_filepath_for_project(
into_path(this->p->get_project_filename(".3mf")));
} catch (const Slic3r::PlaceholderParserError &ex) {
// Show the error with monospaced font.
show_error(this, ex.what(), true);
@@ -15129,7 +15159,8 @@ void Plater::export_gcode_3mf(bool export_all)
unsigned int state = this->p->update_restart_background_process(false, false);
if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID)
return;
default_output_file = this->p->background_process.output_filepath_for_project("");
default_output_file = this->p->background_process.output_filepath_for_project(
into_path(this->p->get_project_filename(".3mf")));
}
catch (const Slic3r::PlaceholderParserError& ex) {
// Show the error with monospaced font.
@@ -15327,7 +15358,7 @@ TriangleMesh Plater::combine_mesh_fff(const ModelObject& mo, int instance_id, st
}
if (instance_id == -1) {
TriangleMesh vols_mesh(mesh);
TriangleMesh vols_mesh(std::move(mesh));
mesh = TriangleMesh();
for (const ModelInstance* i : mo.instances) {
TriangleMesh m = vols_mesh;
@@ -16276,7 +16307,8 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
unsigned int state = this->p->update_restart_background_process(false, false);
if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID)
return;
default_output_file = this->p->background_process.output_filepath_for_project("");
default_output_file = this->p->background_process.output_filepath_for_project(
into_path(this->p->get_project_filename(".3mf")));
} catch (const Slic3r::PlaceholderParserError& ex) {
// Show the error with monospaced font.
show_error(this, ex.what(), true);
+1 -1
View File
@@ -203,7 +203,7 @@ public:
void get_small_btn_sync_pos_size(wxPoint &pt, wxSize &size);
// Orca
static bool should_show_SEMM_buttons();
void show_SEMM_buttons(bool bshow);
void show_SEMM_buttons();
void update_dynamic_filament_list();
PlaterPresetComboBox * printer_combox();
+72 -5
View File
@@ -114,7 +114,8 @@ wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxString too
if (!current_setting.empty()) {
auto compare = [current_setting](string possible_setting) { return current_setting == possible_setting; };
auto iterator = find_if(config_name_index.begin(), config_name_index.end(), compare);
current_index = iterator - config_name_index.begin();
if (iterator != config_name_index.end())
current_index = static_cast<unsigned int>(iterator - config_name_index.begin());
}
auto [sizer, combobox] = create_item_combobox_base(title, tooltip, param, vlist, current_index);
@@ -968,7 +969,7 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
if (m_bambu_cloud_checkbox) m_bambu_cloud_checkbox->Enable(!enabled);
}
#ifdef __WXMSW__
#ifdef __WXMSW__
if (param == "associate_3mf") {
bool pbool = app_config->get("associate_3mf") == "true" ? true : false;
if (pbool) {
@@ -1499,12 +1500,17 @@ void PreferencesDialog::create_items()
auto item_use_free_camera = create_item_checkbox(_L("Use free camera"), _L("If enabled, use free camera. If not enabled, use constrained camera."), "use_free_camera");
g_sizer->Add(item_use_free_camera);
auto swap_pan_rotate = create_item_checkbox(_L("Swap pan and rotate mouse buttons"), _L("If enabled, swaps the left and right mouse buttons pan and rotate functions."), "swap_mouse_buttons");
g_sizer->Add(swap_pan_rotate);
auto reverse_mouse_zoom = create_item_checkbox(_L("Reverse mouse zoom"), _L("If enabled, reverses the direction of zoom with mouse wheel."), "reverse_mouse_wheel_zoom");
g_sizer->Add(reverse_mouse_zoom);
std::vector<wxString> ButtonDragActions = {_L("None"), _L("Pan"), _L("Rotate")};
auto item_left_mouse_drag = create_item_combobox(_L("Left Mouse Drag"), _L("Set the action that dragging the left mouse button should perform."), "left_mouse_drag_action", ButtonDragActions);
g_sizer->Add(item_left_mouse_drag);
auto item_middle_mouse_drag = create_item_combobox(_L("Middle Mouse Drag"), _L("Set the action that dragging the middle mouse button should perform."), "middle_mouse_drag_action", ButtonDragActions);
g_sizer->Add(item_middle_mouse_drag);
auto item_right_mouse_drag = create_item_combobox(_L("Right Mouse Drag"), _L("Set the action that dragging the right mouse button should perform."), "right_mouse_drag_action", ButtonDragActions);
g_sizer->Add(item_right_mouse_drag);
//// CONTROL > Clear my choice on ...
g_sizer->Add(create_item_title(_L("Clear my choice on...")), 1, wxEXPAND);
@@ -1526,6 +1532,64 @@ void PreferencesDialog::create_items()
g_sizer->AddSpacer(FromDIP(10));
sizer_page->Add(g_sizer, 0, wxEXPAND);
//////////////////////////
//// GRAPHICS TAB
/////////////////////////////////////
m_pref_tabs->AppendItem(_L("Graphics"));
f_sizers.push_back(new wxFlexGridSizer(1, 1, v_gap, 0));
g_sizer = f_sizers.back();
g_sizer->AddGrowableCol(0, 1);
//// GRAPHICS > Anti-aliasing
g_sizer->Add(create_item_title(_L("Anti-aliasing")), 1, wxEXPAND);
auto item_antialiasing = create_item_combobox(
_L("MSAA Multiplier"),
_L("Set the Multi-Sample Anti-Aliasing level.\n"
"Higher values result in smoother edges, but the impact on performance is exponential.\n"
"Lower values improve performance, at the cost of jagged edges.\n"
"If disabled, its recommended to enable FXAA to reduce jagged edges with minimal performance impact.\n\n"
"Requires application restart."),
SETTING_OPENGL_AA_SAMPLES,
{_L("Disabled"), "2x", "4x", "8x", "16x"},
{"0", "2", "4", "8", "16"}
);
g_sizer->Add(item_antialiasing);
auto item_fxaa = create_item_checkbox(
_L("FXAA post-processing"),
_L("Applies Fast Approximate Anti-Aliasing as a screen-space pass.\n"
"Useful for disabling or reducing the MSAA setting to improve performance.\n\n"
"Takes effect immediately."),
SETTING_OPENGL_FXAA_ENABLED
);
g_sizer->Add(item_fxaa);
//// GRAPHICS > FPS
g_sizer->Add(create_item_title(_L("FPS")), 1, wxEXPAND);
auto item_fps_cap = create_item_spinctrl(
_L("FPS cap"),
_L("(0 = unlimited)"),
_L("FPS"),
_L("Limits viewport frame rate to reduce GPU load and power usage.\n"
"Set to 0 for unlimited frame rate."),
SETTING_OPENGL_FPS_CAP,
0,
240
);
g_sizer->Add(item_fps_cap);
auto item_fps_overlay = create_item_checkbox(
_L("Show FPS overlay"),
_L("Displays current viewport FPS in the top-right corner."),
SETTING_OPENGL_SHOW_FPS_OVERLAY
);
g_sizer->Add(item_fps_overlay);
g_sizer->AddSpacer(FromDIP(10));
sizer_page->Add(g_sizer, 0, wxEXPAND);
//////////////////////////
//// ONLINE TAB
/////////////////////////////////////
@@ -1794,6 +1858,9 @@ void PreferencesDialog::create_items()
auto item_ams_blacklist = create_item_checkbox(_L("Skip AMS blacklist check"), "", "skip_ams_blacklist_check");
g_sizer->Add(item_ams_blacklist);
auto item_keep_painting = create_item_checkbox(_L("(Experimental) Keep painted feature after mesh change"), _L("Attempt to keep painted features (color/seam/support/fuzzy etc.) after changing the object mesh (such as cut/reload from disk/simplify/fix etc.)\nHighly experimental! Slow and may create artifact."), "keep_painting");
g_sizer->Add(item_keep_painting);
g_sizer->Add(create_item_title(_L("Storage")), 1, wxEXPAND);
auto item_allow_abnormal_storage = create_item_checkbox(_L("Allow Abnormal Storage"), _L("This allows the use of Storage that is marked as abnormal by the Printer.\nUse at your own risk, can cause issues!"), "allow_abnormal_storage");
g_sizer->Add(item_allow_abnormal_storage);
+81
View File
@@ -23,6 +23,81 @@
namespace Slic3r {
namespace GUI {
#ifdef __linux__
// Workaround for #7210: WebKitGTK crashes on vue-resize's hidden <object> probe used by
// older Fluidd/Mainsail pages. Swap that <object> for a <div> shim at appendChild time
// and bridge resize events through a fake contentDocument.defaultView so vue-resize keeps
// working. Workaround proposed by @VittC.
static void inject_vue_resize_workaround(wxWebView *webView)
{
webView->AddUserScript(
"(function() {"
" 'use strict';"
" function isVueResizeObject(el) {"
" return el && el.tagName === 'OBJECT'"
" && el.type === 'text/html'"
" && el.getAttribute('aria-hidden') === 'true'"
" && el.getAttribute('tabindex') === '-1';"
" }"
" function isResizeObserverParent(p) {"
" return p && p.classList && p.classList.contains('resize-observer');"
" }"
" function makeShim(orig, parentForRO) {"
" var shim = document.createElement('div');"
" shim.setAttribute('aria-hidden', 'true');"
" shim.setAttribute('tabindex', '-1');"
" shim.style.display = 'none';"
" var fakeWin = document.createElement('div');"
" var ro = null;"
" var origRemoveEL = fakeWin.removeEventListener.bind(fakeWin);"
" fakeWin.removeEventListener = function(type, fn, opts) {"
" origRemoveEL(type, fn, opts);"
" if (type === 'resize' && ro) { ro.disconnect(); ro = null; }"
" };"
" Object.defineProperty(shim, 'contentDocument', {"
" configurable: true,"
" get: function() { return { defaultView: fakeWin }; }"
" });"
" Object.defineProperty(shim, 'contentWindow', {"
" configurable: true,"
" get: function() { return fakeWin; }"
" });"
" if (typeof orig.onload === 'function') { shim.onload = orig.onload; }"
" queueMicrotask(function() {"
" if (parentForRO && typeof ResizeObserver !== 'undefined') {"
" ro = new ResizeObserver(function() {"
" fakeWin.dispatchEvent(new Event('resize'));"
" });"
" ro.observe(parentForRO);"
" }"
" if (typeof shim.onload === 'function') {"
" try { shim.onload(new Event('load')); } catch (e) {}"
" }"
" shim.dispatchEvent(new Event('load'));"
" });"
" return shim;"
" }"
" var origAppend = Node.prototype.appendChild;"
" Node.prototype.appendChild = function(child) {"
" if (isResizeObserverParent(this) && isVueResizeObject(child)) {"
" return origAppend.call(this, makeShim(child, this));"
" }"
" return origAppend.call(this, child);"
" };"
" var origInsertBefore = Node.prototype.insertBefore;"
" Node.prototype.insertBefore = function(child, ref) {"
" if (isResizeObserverParent(this) && isVueResizeObject(child)) {"
" return origInsertBefore.call(this, makeShim(child, this), ref);"
" }"
" return origInsertBefore.call(this, child, ref);"
" };"
" console.log('[vr-fix] vue-resize WebKitGTK patch active');"
"})();",
wxWEBVIEW_INJECT_AT_DOCUMENT_START
);
}
#endif
PrinterWebView::PrinterWebView(wxWindow *parent)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
, m_browser(nullptr)
@@ -43,6 +118,8 @@ PrinterWebView::PrinterWebView(wxWindow *parent)
}
#ifdef __linux__
inject_vue_resize_workaround(m_browser);
auto cookiesPath = boost::filesystem::path(data_dir() + "/cache/cookies.db");
auto wv = static_cast<WebKitWebView*>(m_browser->GetNativeBackend());
auto wv_ctx = webkit_web_view_get_context(wv);
@@ -165,6 +242,10 @@ void PrinterWebView::SendAPIKey()
)",
m_apikey);
m_browser->RemoveAllUserScripts();
#ifdef __linux__
// Re-inject the vue-resize/WebKitGTK workaround that RemoveAllUserScripts just cleared.
inject_vue_resize_workaround(m_browser);
#endif
m_browser->AddUserScript(script);
m_browser->Reload();
+32 -10
View File
@@ -41,7 +41,7 @@
#include "UnsavedChangesDialog.hpp"
#include "SavePresetDialog.hpp"
#include "EditGCodeDialog.hpp"
#include "MultiChoiceDialog.hpp"
#include "MsgDialog.hpp"
#include "Notebook.hpp"
@@ -426,9 +426,12 @@ void Tab::create_preset_tab()
});
m_top_sizer->Add(m_mode_icon, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(SidebarProps::WideSpacing()));
m_mode_view = new ModeSwitchButton(m_top_panel);
m_mode_view->SetSelection(mode_to_selection(wxGetApp().get_saved_mode()));
if (wxGetApp().get_mode() == comDevelop)
if (wxGetApp().get_mode() == comDevelop) {
m_mode_view->SetSelection(mode_to_selection(comExpert));
m_mode_view->Enable(false);
} else {
m_mode_view->SetSelection(mode_to_selection(wxGetApp().get_saved_mode()));
}
m_top_sizer->AddSpacer(FromDIP(SidebarProps::ElementSpacing()));
m_top_sizer->Add( m_mode_view, 0, wxALIGN_CENTER_VERTICAL);
}
@@ -1556,8 +1559,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
if (opt_key == "single_extruder_multi_material" ){
const auto bSEMM = m_config->opt_bool("single_extruder_multi_material");
wxGetApp().sidebar().show_SEMM_buttons(bSEMM);
wxGetApp().sidebar().show_SEMM_buttons();
wxGetApp().get_tab(Preset::TYPE_PRINT)->update();
}
@@ -1608,8 +1610,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
if (opt_key == "single_extruder_multi_material" ){
const auto bSEMM = m_config->opt_bool("single_extruder_multi_material");
wxGetApp().sidebar().show_SEMM_buttons(bSEMM);
wxGetApp().sidebar().show_SEMM_buttons();
wxGetApp().get_tab(Preset::TYPE_PRINT)->update();
}
@@ -2449,6 +2450,7 @@ void TabPrint::build()
optgroup->append_single_option_line("sparse_infill_density", "strength_settings_infill#sparse-infill-density");
optgroup->append_single_option_line("fill_multiline", "strength_settings_infill#fill-multiline");
optgroup->append_single_option_line("sparse_infill_pattern", "strength_settings_infill#sparse-infill-pattern");
optgroup->append_single_option_line("gyroid_optimized", "strength_settings_patterns#gyroid_optimized");
optgroup->append_single_option_line("infill_direction", "strength_settings_infill#direction");
optgroup->append_single_option_line("sparse_infill_rotate_template", "strength_settings_infill_rotation_template_metalanguage");
optgroup->append_single_option_line("skin_infill_density", "strength_settings_patterns#locked-zag");
@@ -5094,6 +5096,8 @@ void TabPrinter::build_fff()
line.append_option(optgroup->get_option("fan_speedup_overhangs"));
optgroup->append_line(line);
optgroup->append_single_option_line("fan_kickstart", "printer_basic_information_cooling_fan#fan-kick-start-time");
// ORCA: PWM floor for fans that won't spool at low duty cycles.
optgroup->append_single_option_line("part_cooling_fan_min_pwm", "printer_basic_information_cooling_fan#minimum-non-zero-part-cooling-fan-speed");
optgroup = page->new_optgroup(L("Extruder Clearance"), "param_extruder_clearance");
optgroup->append_single_option_line("extruder_clearance_radius", "printer_basic_information_extruder_clearance#radius");
@@ -5590,6 +5594,7 @@ if (is_marlin_flavor)
optgroup->append_single_option_line("wipe_tower_type", "printer_multimaterial_wipe_tower");
optgroup->append_single_option_line("purge_in_prime_tower", "printer_multimaterial_wipe_tower#purge-in-prime-tower");
optgroup->append_single_option_line("enable_filament_ramming", "printer_multimaterial_wipe_tower#enable-filament-ramming");
optgroup->append_single_option_line("tool_change_on_wipe_tower", "printer_multimaterial_wipe_tower#tool-change-on-wipe-tower");
optgroup = page->new_optgroup(L("Single extruder multi-material parameters"), "param_settings");
@@ -6184,6 +6189,12 @@ void TabPrinter::toggle_options()
}
}
}
// Orca: "Tool change on wipe tower" only makes sense for multi-extruder (multi-toolhead) printers
// using a Type 2 wipe tower. SEMM already always travels to the tower as part of the purge,
// so the option is irrelevant there.
const size_t extruders_count = m_config->option<ConfigOptionFloats>("nozzle_diameter")->size();
toggle_option("tool_change_on_wipe_tower", !bSEMM && supports_wipe_tower_2 && extruders_count > 1);
}
wxString extruder_number;
long val = 1;
@@ -7752,7 +7763,15 @@ wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &dep
presets.Add(from_u8(preset.name));
}
wxMultiChoiceDialog dlg(parent, deps.dialog_title, deps.dialog_label, presets);
if(deps.type == Preset::TYPE_PRINTER){
deps.dialog_title = "Compatible printers";
deps.dialog_label = "Select printers";
}else{
deps.dialog_title = "Compatible process profiles";
deps.dialog_label = "Select profiles";
}
MultiChoiceDialog dlg(parent, deps.dialog_label, deps.dialog_title, presets);
wxGetApp().UpdateDlgDarkUI(&dlg);
// Collect and set indices of depending_presets marked as compatible.
wxArrayInt selections;
@@ -7765,13 +7784,16 @@ wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &dep
break;
}
dlg.SetSelections(selections);
dlg.SetSize(FromDIP(wxSize(360, 480)));
std::vector<std::string> value;
// Show the dialog.
if (dlg.ShowModal() == wxID_OK) {
selections.Clear();
selections = dlg.GetSelections();
for (auto idx : selections)
value.push_back(presets[idx].ToUTF8().data());
// leave list empty if all items checked. this will check "All" checkbox automatically. also fixes unnecessary config change
if(selections.GetCount() != presets.GetCount())
for (auto idx : selections)
value.push_back(presets[idx].ToUTF8().data());
if (value.empty()) {
deps.checkbox->SetValue(1);
deps.btn->Disable();
+1 -1
View File
@@ -147,7 +147,6 @@ protected:
//BBS: GUI refactor
wxPanel* m_top_panel;
ScalableButton* m_mode_icon; // ORCA m_static_title replacement
wxBoxSizer* m_main_sizer;
wxBoxSizer* m_top_sizer;
wxBoxSizer* m_top_left_sizer;
@@ -308,6 +307,7 @@ public:
int m_update_cnt = 0;
ModeSwitchButton *m_mode_view = nullptr;
ScalableButton* m_mode_icon = nullptr; // ORCA m_static_title replacement
SwitchButton *m_extruder_switch = nullptr;
MultiSwitchButton *m_variant_combo = nullptr;
+4 -1
View File
@@ -4,6 +4,7 @@
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/iostreams/detail/select.hpp>
#include <boost/log/trivial.hpp>
#include <string.h>
#include "I18N.hpp"
#include "libslic3r/AppConfig.hpp"
@@ -516,7 +517,9 @@ void GuideFrame::OnScriptMessage(wxWebViewEvent &evt)
if (agent) {
agent->set_country_code(country_code);
if (wxGetApp().is_user_login()) {
agent->user_logout();
BOOST_LOG_TRIVIAL(info) << "logout: user_logout on user_guide_finish";
// agent->user_logout();
wxGetApp().request_user_logout();
}
}
}
+92 -1
View File
@@ -5,6 +5,7 @@
#include "libslic3r/AppConfig.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "libslic3r_version.h"
#include <wx/sizer.h>
@@ -371,7 +372,97 @@ void ZUserLogin::OnScriptMessage(wxWebViewEvent &evt)
{
m_AutotestToken = j["data"]["token"];
}
if (strCmd == "user_login") {
if (strCmd == "user_ticket_login") {
auto* agent = wxGetApp().getAgent();
if (!agent || !m_cloud_agent || !j.contains("data") || !j["data"].is_object() || !j["data"].contains("ticket")) {
wxMessageBox(_L("Login failed. Please try again."), _L("Login"), wxICON_WARNING);
return;
}
const auto provider = m_cloud_agent->get_id();
std::string ticket = j["data"]["ticket"].get<std::string>();
unsigned int token_http_code = 0;
std::string token_body;
int token_result = agent->get_my_token(ticket, &token_http_code, &token_body, provider);
if (token_result != 0) {
BOOST_LOG_TRIVIAL(warning) << "embedded_login: get_my_token failed, http_code=" << token_http_code;
wxMessageBox(_L("Login failed. Please try again."), _L("Login"), wxICON_WARNING);
return;
}
std::string access_token;
std::string refresh_token;
std::string expires_in_str;
std::string refresh_expires_in_str;
try {
json token_j = json::parse(token_body);
if (token_j.contains("accessToken"))
access_token = token_j["accessToken"].get<std::string>();
if (token_j.contains("refreshToken"))
refresh_token = token_j["refreshToken"].get<std::string>();
if (token_j.contains("expiresIn"))
expires_in_str = std::to_string(token_j["expiresIn"].get<double>());
if (token_j.contains("refreshExpiresIn"))
refresh_expires_in_str = std::to_string(token_j["refreshExpiresIn"].get<double>());
} catch (...) {
wxMessageBox(_L("Login failed. Please try again."), _L("Login"), wxICON_WARNING);
return;
}
if (access_token.empty()) {
wxMessageBox(_L("Login failed. Please try again."), _L("Login"), wxICON_WARNING);
return;
}
unsigned int profile_http_code = 0;
std::string profile_body;
int profile_result = agent->get_my_profile(access_token, &profile_http_code, &profile_body, provider);
if (profile_result != 0) {
BOOST_LOG_TRIVIAL(warning) << "embedded_login: get_my_profile failed, http_code=" << profile_http_code;
wxMessageBox(_L("Login failed. Please try again."), _L("Login"), wxICON_WARNING);
return;
}
std::string user_id;
std::string user_name;
std::string user_account;
std::string user_avatar;
try {
json user_j = json::parse(profile_body);
if (user_j.contains("uidStr"))
user_id = user_j["uidStr"].get<std::string>();
if (user_j.contains("name"))
user_name = user_j["name"].get<std::string>();
if (user_j.contains("avatar"))
user_avatar = user_j["avatar"].get<std::string>();
if (user_j.contains("account"))
user_account = user_j["account"].get<std::string>();
} catch (...) {
BOOST_LOG_TRIVIAL(warning) << "embedded_login: profile JSON parse failed";
}
json login_j;
login_j["command"] = "user_login";
login_j["data"]["autotest_token"] = m_AutotestToken;
login_j["data"]["refresh_token"] = refresh_token;
login_j["data"]["token"] = access_token;
login_j["data"]["expires_in"] = expires_in_str;
login_j["data"]["refresh_expires_in"] = refresh_expires_in_str;
login_j["data"]["user"]["uid"] = user_id;
login_j["data"]["user"]["name"] = user_name;
login_j["data"]["user"]["account"] = user_account;
login_j["data"]["user"]["avatar"] = user_avatar;
std::string message_json = login_j.dump();
// End modal dialog first to unblock event loop before processing callbacks
EndModal(wxID_OK);
// Handle message after modal dialog ends to avoid deadlock
// Use wxTheApp->CallAfter to ensure it runs after modal loop exits
wxTheApp->CallAfter([message_json, provider]() { wxGetApp().handle_script_message(message_json, provider); });
}
else if (strCmd == "user_login") {
j["data"]["autotest_token"] = m_AutotestToken;
std::string message_json = j.dump();
+225
View File
@@ -0,0 +1,225 @@
#include "CheckList.hpp"
#include "slic3r/GUI/GUI_App.hpp"
CheckList::CheckList(
wxWindow* parent,
const wxArrayString& choices,
long scroll_style
)
: wxWindow(parent, wxID_ANY)
, m_search(this, "search", 16)
, m_menu(this, "filter", 16)
, m_first_load(true)
{
Freeze();
w_sizer = new wxBoxSizer(wxVERTICAL);
f_sizer = new wxBoxSizer(wxHORIZONTAL);
f_bar = new wxPanel(this, wxID_ANY);
f_bar->SetBackgroundColour(parent->GetBackgroundColour());
m_filter_box = new TextInput(f_bar, "", "", "", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
m_filter_box->SetIcon(m_search.bmp());
m_filter_box->SetMinSize(FromDIP(wxSize(200,24)));
m_filter_box->SetSize(FromDIP(wxSize(-1,24)));
m_filter_box->SetFocus();
m_filter_ctrl = m_filter_box->GetTextCtrl();
m_filter_ctrl->SetFont(Label::Body_13);
m_filter_ctrl->SetSize(wxSize(-1, FromDIP(16))); // Centers text vertically
m_filter_ctrl->SetHint(_L("Type to filter..."));
m_filter_ctrl->Bind(wxEVT_TEXT, [this](auto &e) {Filter(m_filter_ctrl->GetValue());});
m_filter_ctrl->Bind(wxEVT_TEXT_ENTER, [this](auto &e) {Filter(m_filter_ctrl->GetValue());});
m_filter_ctrl->Bind(wxEVT_SET_FOCUS, [this](auto &e) {Filter(m_filter_ctrl->GetValue());e.Skip();});
m_filter_ctrl->Bind(wxEVT_KILL_FOCUS, [this](auto &e) {Filter(m_filter_ctrl->GetValue());e.Skip();});
f_sizer->Add(m_filter_box, 1, wxEXPAND);
Bind(wxEVT_SET_FOCUS, [this](auto &e) {m_filter_box->SetFocus();});
fb_sizer = new wxBoxSizer(wxHORIZONTAL);
auto create_btn = [this] (wxString title, bool select){
auto btn = new wxStaticText(f_bar, wxID_ANY, title);
btn->SetForegroundColour("#009687");
btn->SetCursor(wxCURSOR_HAND);
btn->SetFont(Label::Body_13);
btn->Bind(wxEVT_LEFT_DOWN, [this, select](wxMouseEvent &e) {SelectAll(select);});
fb_sizer->Add(btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(10));
};
f_sizer->Add(fb_sizer,0 ,wxALIGN_CENTER_VERTICAL);
create_btn(_L("All") , true);
create_btn(_L("None"), false);
m_menu_button = new wxStaticBitmap(f_bar, wxID_ANY, m_menu.bmp());
m_menu_button->SetCursor(wxCURSOR_HAND);
m_menu_button->Bind(wxEVT_LEFT_DOWN, &CheckList::ShowMenu, this);
f_sizer->Add(m_menu_button,0 ,wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(10));
f_bar->SetSizerAndFit(f_sizer);
w_sizer->Add(f_bar, 0, wxEXPAND);
auto spacer = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, FromDIP(3)));
spacer->SetBackgroundColour(parent->GetBackgroundColour());
w_sizer->Add(spacer, 0, wxEXPAND);
s_sizer = new wxBoxSizer(wxVERTICAL);
m_scroll_area = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, scroll_style);
m_scroll_area->SetScrollRate(0, 10);
m_scroll_area->SetSizer(s_sizer);
m_scroll_area->SetBackgroundColour(parent->GetBackgroundColour());
m_scroll_area->Bind(wxEVT_RIGHT_DOWN, &CheckList::ShowMenu, this);
m_scroll_area->DisableFocusFromKeyboard();
m_info = new wxStaticText(m_scroll_area, wxID_ANY, "");
m_info->SetFont(Label::Body_13);
s_sizer->Add(m_info, 1, wxALIGN_CENTER_HORIZONTAL | wxALL, FromDIP(10));
m_info->Hide();
m_info_nonsel = _L("No selected items...");
m_info_allsel = _L("All items selected...");
m_info_empty = _L("No matching items...");
SetBackgroundColour(StateColor::darkModeColorFor("#DBDBDB")); // draws border on wxScrolledWindow
w_sizer->Add(m_scroll_area, 1, wxEXPAND | wxALL, FromDIP(1)); // 1 for border
s_sizer->Layout();
m_list_size = choices.size();
m_checks.reserve(m_list_size);
auto margin = FromDIP(2);
wxCheckBox* cb;
for (size_t i = 0; i < m_list_size; ++i){
cb = new wxCheckBox(m_scroll_area, wxID_ANY, choices[i]);
m_checks.emplace_back(cb);
s_sizer->Add(cb, 0, wxALL, margin);
}
m_scroll_area->FitInside();
s_sizer->Layout();
SetSizer(w_sizer);
Layout();
Thaw();
}
void CheckList::SetSelections(wxArrayInt sel_array){
if(!m_first_load){
for (size_t i = 0; i < m_list_size; ++i)
Check(i, false);
m_first_load = false;
}
for (int i : sel_array)
Check(i, true);
}
wxArrayInt CheckList::GetSelections()
{
wxArrayInt checks;
for (size_t i = 0; i < m_list_size; ++i)
if (m_checks[i]->GetValue())
checks.push_back(i);
return checks;
}
void CheckList::Check(int i, bool checked)
{
if (i > -1 && i < m_list_size)
m_checks[i]->SetValue(checked);
}
void CheckList::SelectAll(bool value)
{
for (size_t i = 0; i < m_list_size; ++i)
Check(i, value);
}
void CheckList::SelectVisible(bool value)
{
auto filter = m_filter_ctrl->GetValue().Lower();
if((!value && filter == "::unsel") || (value && filter == "::sel"))
m_filter_ctrl->SetValue("");
for (size_t i = 0; i < m_list_size; ++i)
if(m_checks[i]->IsShown())
Check(i, value);
}
bool CheckList::IsChecked(int i)
{
if (i > -1 && i < m_list_size)
return false;
return m_checks[i]->GetValue();
}
void CheckList::Filter(const wxString& filterText)
{
Freeze();
auto filter = filterText.Lower();
auto c_text = m_filter_ctrl->GetValue().Lower();
if(filter == "::sel" || filter == "::nonsel"){
if(c_text != filter){ // not text input
m_filter_ctrl->SetValue(filter);
m_filter_ctrl->SetSelection(0,-1);
}
fb_sizer->Show(false);
if (filter == "::sel")
for (auto& cb : m_checks) cb->Show(cb->GetValue());
else
for (auto& cb : m_checks) cb->Show(!cb->GetValue());
}
else{
bool clear = filterText.IsEmpty();
fb_sizer->Show(clear);
for (auto& cb : m_checks)
cb->Show(clear || cb->GetLabel().Lower().Contains(filter));
}
m_info->Show();
for (size_t i = 0; i < m_list_size; ++i) {
if (m_checks[i]->IsShown()){
m_info->Hide();
break;
}
}
if (m_info->IsShown())
m_info->SetLabel(filter == "::sel" ? m_info_nonsel : filter == "::nonsel" ? m_info_allsel : m_info_empty);
m_scroll_area->FitInside();
f_sizer->Layout();
Thaw();
}
void CheckList::ShowMenu(wxMouseEvent &evt)
{
bool filtering = !m_filter_ctrl->GetValue().IsEmpty();
bool list_empty = m_info->IsShown();
wxMenu m;
m.Append(wxID_FILE1, _L("Select All" ))->Enable(!filtering);
m.Append(wxID_FILE2, _L("Deselect All"))->Enable(!filtering);
m.AppendSeparator();
m.Append(wxID_FILE3, _L("Select visible" ))->Enable(!list_empty && filtering);
m.Append(wxID_FILE4, _L("Deselect visible"))->Enable(!list_empty && filtering);
m.AppendSeparator();
m.Append(wxID_FILE5, _L("Filter selected" ));
m.Append(wxID_FILE6, _L("Filter nonSelected"));
m.Bind(wxEVT_MENU, [this](wxCommandEvent& e) {
switch (e.GetId()){
case wxID_FILE1: SelectAll(true) ; break;
case wxID_FILE2: SelectAll(false) ; break;
case wxID_FILE3: SelectVisible(true) ; break;
case wxID_FILE4: SelectVisible(false); break;
case wxID_FILE5: Filter("::sel") ; break;
case wxID_FILE6: Filter("::nonsel") ; break;
default: break;
}
},wxID_FILE1, wxID_FILE6);
wxWindow* src = dynamic_cast<wxWindow*>(evt.GetEventObject());
if (!src) return;
wxPoint screen_pos = src->ClientToScreen(evt.GetPosition());
wxPoint local_pos = ScreenToClient(screen_pos);
PopupMenu(&m, local_pos);
}
+59
View File
@@ -0,0 +1,59 @@
#ifndef slic3r_GUI_CHECKLIST_hpp_
#define slic3r_GUI_CHECKLIST_hpp_
#include "../wxExtensions.hpp"
#include "Label.hpp"
#include "TextInput.hpp"
#include <wx/wx.h>
#include <vector>
#include <wx/scrolwin.h>
#include <wx/menu.h>
class CheckList : public wxWindow
{
public:
CheckList(
wxWindow* parent,
const wxArrayString& choices = wxArrayString(),
long scroll_style = wxVSCROLL
);
wxArrayInt GetSelections();
void SetSelections(wxArrayInt sel_array);
void Check(int i, bool checked);
bool IsChecked(int i);
void Filter(const wxString& filterText);
void SelectAll(bool value);
void SelectVisible(bool value);
private:
void ShowMenu(wxMouseEvent &e);
std::vector<wxCheckBox*> m_checks;
int m_list_size;
bool m_first_load;
wxBoxSizer* w_sizer;
wxPanel* f_bar;
wxBoxSizer* f_sizer;
TextInput* m_filter_box;
wxTextCtrl* m_filter_ctrl;
wxBoxSizer* fb_sizer;
wxStaticBitmap* m_menu_button;
wxScrolledWindow* m_scroll_area;
wxBoxSizer* s_sizer;
wxStaticText* m_info;
wxString m_info_nonsel;
wxString m_info_allsel;
wxString m_info_empty;
ScalableBitmap m_search;
ScalableBitmap m_menu;
};
#endif // !slic3r_GUI_CheckList_hpp_
+37 -7
View File
@@ -167,6 +167,30 @@ bool DropDown::HasDismissLongTime()
(now - dismissTime).total_milliseconds() >= 20;
}
void DropDown::Popup(wxWindow *focus)
{
#ifdef __WXGTK__
if (!mainDropDown && m_widget) {
// Data-view cell editors can receive focus before wxGTK infers a
// native popup parent, so provide the current toplevel explicitly.
GtkWindow *transient_parent = nullptr;
for (wxWindow *win = GetParent(); win; win = win->GetParent()) {
GtkWidget *widget = static_cast<GtkWidget *>(win->GetHandle());
if (!widget)
continue;
GtkWidget *top = gtk_widget_get_toplevel(widget);
if (GTK_IS_WINDOW(top)) {
transient_parent = GTK_WINDOW(top);
break;
}
}
if (transient_parent)
gtk_window_set_transient_for(GTK_WINDOW(m_widget), transient_parent);
}
#endif
PopupWindow::Popup(focus);
}
void DropDown::paintEvent(wxPaintEvent& evt)
{
// depending on your system you may need to look at double-buffered dcs
@@ -554,7 +578,8 @@ void DropDown::messureSize()
wxWindow::SetSize(szContent);
#ifdef __WXGTK__
// Gtk has a wrapper window for popup widget
gtk_window_resize (GTK_WINDOW (m_widget), szContent.x, szContent.y);
if (szContent.x > 0 && szContent.y > 0)
gtk_window_resize (GTK_WINDOW (m_widget), szContent.x, szContent.y);
#endif
if (!groups.empty() && subDropDown == nullptr) {
subDropDown = new DropDown(items);
@@ -564,11 +589,6 @@ void DropDown::messureSize()
subDropDown->use_content_width = true;
subDropDown->Create(GetParent());
#ifdef __WXGTK__
// Orca: Keep the wx parent as the combobox so wxPopupTransientWindow installs
// its capture handlers on the main dropdown, but make the native GTK
// popup transient for the currently open popup to satisfy Wayland's
// xdg-shell rule that a popup's parent must be the topmost mapped popup.
gtk_window_set_transient_for(GTK_WINDOW(subDropDown->GetHandle()), GTK_WINDOW(GetHandle()));
// Orca: On Wayland, while the sub holds an xdg_popup grab, motion events for
// the cursor over main may not be delivered (Mutter drops motion
// outside the grabbing surface). Poll on idle and synthesize a
@@ -636,8 +656,11 @@ void DropDown::autoPosition()
// may exceed
auto drect = wxDisplay(GetParent()).GetGeometry();
if (GetPosition().y + size.y + 10 > drect.GetBottom()) {
int available_height = drect.GetBottom() - GetPosition().y - 10;
if (available_height < rowSize.y * 2)
return;
if (use_content_width && count <= 15) size.x += 6;
size.y = drect.GetBottom() - GetPosition().y - 10;
size.y = available_height;
wxWindow::SetSize(size);
if (selection >= 0) {
if (offset.y + rowSize.y * (selection + 1) > size.y)
@@ -727,6 +750,13 @@ void DropDown::mouseMove(wxMouseEvent &event)
drop.group = items[-index - 2].group_key;
drop.need_sync = true;
drop.messureSize();
#ifdef __WXGTK__
// wxGTK wraps popup contents in a native GtkWindow. Make the submenu
// transient for the currently mapped parent popup window before
// positioning/showing it, so wlroots/Hyprland sees the topmost parent.
if (m_widget && drop.m_widget)
gtk_window_set_transient_for(GTK_WINDOW(drop.m_widget), GTK_WINDOW(m_widget));
#endif
drop.autoPosition();
drop.paintNow();
if (!drop.IsShown())
+2
View File
@@ -105,6 +105,8 @@ public:
bool HasDismissLongTime();
void Popup(wxWindow *focus = nullptr) override;
protected:
void Dismiss() override;
+2 -1
View File
@@ -42,7 +42,8 @@ static std::map<wxColour, wxColour> gDarkColors{
{"#D7E8DE", "#1F2B27"}, // rgb(215, 232, 222) Not Used anymore // Leftover from BBS
{"#2B3436", "#808080"}, // rgb(43, 52, 54) Not Used anymore // Leftover from BBS. Was used as main fill color of icons
{"#ABABAB", "#ABABAB"},
{"#D9D9D9", "#2D2D32"}, // rgb(217, 217, 217) Sidebar > Toggle button track color
{"#D9D9D9", "#27272A"}, // rgb(217, 217, 217) Sidebar > Toggle button track color
{"#FFFEFE", "#D9D9D9"}, // rgb(255, 254, 254) Sidebar > Toggle button thumb color
{"#EBF9F0", "#293F34"},
//{"#F0F0F0", "#4C4C54"},
// ORCA
+12 -14
View File
@@ -193,20 +193,18 @@ void StaticBox::doRender(wxDC& dc)
if ((border_width && border_color.count() > 0) || background_color.count() > 0) {
wxRect rc(0, 0, size.x, size.y);
if (border_width && border_color.count() > 0) {
if (dc.GetContentScaleFactor() == 1.0) {
int d = floor(border_width / 2.0);
int d2 = floor(border_width - 1);
rc.x += d;
rc.width -= d2;
rc.y += d;
rc.height -= d2;
} else {
int d = 1;
rc.x += d;
rc.width -= d;
rc.y += d;
rc.height -= d;
}
const double scale = dc.GetContentScaleFactor();
// Snap rect edges to physical pixel boundaries so the 1px pen doesn't straddle a pixel boundary
auto snap = [&](int logical) -> int {
return (int)(ceil(logical * scale) / scale);
};
int deflate = snap(border_width / 2.0); // at 175%: snap(0.5) = snap→1/1.75 ≈ 1
rc.x += deflate;
rc.y += deflate;
rc.width -= deflate * 2;
rc.height -= deflate * 2;
dc.SetPen(wxPen(border_color.colorForStates(states), border_width, border_style));
} else {
dc.SetPen(wxPen(background_color.colorForStates(states)));
+84 -51
View File
@@ -220,14 +220,40 @@ void SwitchButton::update()
ModeSwitchButton::ModeSwitchButton(wxWindow* parent, wxWindowID id)
{
background_color = StateColor(
std::make_pair(wxColour(0xF1, 0xF1, 0xF1), (int) StateColor::Disabled),
std::make_pair(wxColour(0xE3, 0xE3, 0xE3), (int) StateColor::Pressed),
std::make_pair(wxColour(0xD9, 0xD9, 0xD9), (int) StateColor::Normal));
std::make_pair(wxColour("#D9D9D9"), (int) StateColor::Disabled),
std::make_pair(wxColour("#D9D9D9"), (int) StateColor::Normal)
);
border_color = StateColor(
std::make_pair(wxColour(0xEA, 0xEA, 0xEA), (int) StateColor::Disabled),
std::make_pair(wxColour(0xBC, 0xBC, 0xBC), (int) StateColor::Hovered),
std::make_pair(wxColour(0xC8, 0xC8, 0xC8), (int) StateColor::Focused),
std::make_pair(wxColour(0xCE, 0xCE, 0xCE), (int) StateColor::Normal));
std::make_pair(wxColour("#D9D9D9"), (int) StateColor::Disabled),
std::make_pair(wxColour("#D9D9D9"), (int) StateColor::Hovered | ~StateColor::Focused),
std::make_pair(wxColour("#26A69A"), (int) StateColor::Focused),
std::make_pair(wxColour("#D9D9D9"), (int) StateColor::Normal)
);
track_background = StateColor(
std::make_pair(wxColour("#009688"), (int) StateColor::Disabled),
std::make_pair(wxColour("#009688"), (int) StateColor::Normal)
);
track_border = StateColor(
std::make_pair(wxColour("#D9D9D9"), (int) StateColor::Disabled),
std::make_pair(wxColour("#009688"), (int) StateColor::Hovered | ~StateColor::Focused),
std::make_pair(wxColour("#26A69A"), (int) StateColor::Focused),
std::make_pair(wxColour("#009688"), (int) StateColor::Normal)
);
dot_active = StateColor(
std::make_pair(wxColour("#FFFEFE"), (int) StateColor::Disabled),
std::make_pair(wxColour("#FFFEFE"), (int) StateColor::Normal)
);
dot_dimmed = StateColor(
std::make_pair(wxColour("#EEEEEE"), (int) StateColor::Disabled),
std::make_pair(wxColour("#EEEEEE"), (int) StateColor::Normal)
);
text_color = StateColor(
std::make_pair(wxColour("#6B6B6B"), (int) StateColor::Disabled),
std::make_pair(wxColour("#6B6B6B"), (int) StateColor::Normal)
);
state_handler.attach(std::vector<StateColor const*>{&dot_active, &dot_dimmed, &text_color});
state_handler.update_binds();
StaticBox::Create(parent, id, wxDefaultPosition, wxDefaultSize, 0);
SetBackgroundColour(StaticBox::GetParentBackgroundColor(parent));
@@ -263,7 +289,7 @@ void ModeSwitchButton::SelectAndNotify(int selection)
void ModeSwitchButton::Rescale()
{
const wxSize button_size = FromDIP(wxSize(48, 20));
const wxSize button_size = FromDIP(wxSize(48, 18));
SetMinSize(button_size);
SetMaxSize(button_size);
SetSize(button_size);
@@ -274,63 +300,70 @@ void ModeSwitchButton::Rescale()
bool ModeSwitchButton::Enable(bool enable /* = true */)
{
const bool changed = StaticBox::Enable(enable);
if (changed)
if (changed){
wxCommandEvent e(EVT_ENABLE_CHANGED);
e.SetEventObject(this);
GetEventHandler()->ProcessEvent(e);
m_enabled = enable; // IsEnabled() not works because variable changes after paint event
Refresh();
}
return changed;
}
void ModeSwitchButton::doRender(wxDC& dc)
{
dc.SetPen(*wxTRANSPARENT_PEN);
dc.SetBrush(wxBrush(GetBackgroundColour()));
dc.DrawRectangle(GetClientRect());
const wxRect bounds = GetClientRect().Deflate(1);
const wxRect bounds = GetClientRect();
if (bounds.width <= 0 || bounds.height <= 0)
return;
const int states = state_handler.states();
const bool hovered = (states & StateHandler::Hovered) != 0;
const bool focused = (states & StateHandler::Focused) != 0;
const bool disabled = !IsEnabled();
const wxColour track_fill = disabled ? wxColour(0xD0, 0xD0, 0xD4) :
m_pressed ? wxColour(0x5A, 0x5D, 0x64) : wxColour(0x66, 0x69, 0x70);
const wxColour track_border = disabled ? wxColour(0xDD, 0xDD, 0xE0) :
focused ? wxColour("#009688") :
hovered ? wxColour(0x7A, 0x7D, 0x84) : wxColour(0x75, 0x78, 0x7F);
const wxColour active_fill = disabled ? wxColour(0x9E, 0xBE, 0xB9) :
m_pressed ? wxColour(0x00877B) : wxColour("#009688");
const wxColour active_dot = disabled ? wxColour(0xEC, 0xF4, 0xF2) : wxColour(0xB7, 0xEB, 0xE3);
const wxColour inactive_dot = disabled ? wxColour(0xF2, 0xF2, 0xF4) : wxColour(0xB5, 0xB7, 0xBD);
const wxColour thumb_fill = disabled ? wxColour(0xFA, 0xFA, 0xFA) : *wxWHITE;
const wxColour thumb_border = disabled ? wxColour(0xE7, 0xE7, 0xEA) : wxColour(0xDD, 0xDF, 0xE3);
dc.SetPen(wxPen(track_border, 1));
dc.SetBrush(wxBrush(track_fill));
dc.DrawRoundedRectangle(bounds, bounds.height / 2.0);
const wxRect thumb = thumb_rect_for(m_selection);
const int fill_right = std::min(bounds.GetRight(), thumb.GetX() + thumb.GetWidth() / 2 + FromDIP(2));
wxRect active(bounds.x, bounds.y, fill_right - bounds.x + 1, bounds.height);
dc.SetPen(*wxTRANSPARENT_PEN);
dc.SetBrush(wxBrush(active_fill));
dc.DrawRoundedRectangle(active, bounds.height / 2.0);
dc.SetBrush(wxBrush(GetBackgroundColour()));
dc.DrawRectangle(bounds);
const int dot_radius = std::max(FromDIP(1), thumb.height / 7);
for (int idx = 0; idx < 3; ++idx) {
if (idx == m_selection)
continue;
int states = state_handler.states();
double v_center = bounds.height / 2.0;
const wxRect slot = thumb_rect_for(idx);
const wxPoint center(slot.GetX() + slot.GetWidth() / 2, slot.GetY() + slot.GetHeight() / 2);
dc.SetBrush(wxBrush(idx < m_selection ? active_dot : inactive_dot));
dc.DrawCircle(center, dot_radius);
// Background
dc.SetPen(wxPen(border_color.colorForStates(states), 1));
dc.SetBrush(wxBrush(background_color.colorForStates(states)));
dc.DrawRoundedRectangle(bounds, v_center);
if (m_enabled) {
double dot_dist = (bounds.width - bounds.height) * 0.50;
// Track
dc.SetPen(wxPen(track_border.colorForStates(states), 1));
dc.SetBrush(wxBrush(track_background.colorForStates(states)));
wxRect track_rc = bounds;
track_rc.width = int(v_center * 2.0 + dot_dist * m_selection);
dc.DrawRoundedRectangle(track_rc, v_center);
// Dots
dc.SetPen(*wxTRANSPARENT_PEN);
for (int idx = 0; idx < 3; ++idx) {
dc.SetBrush(wxBrush((idx <= m_selection ? dot_active : dot_dimmed).colorForStates(states)));
dc.DrawCircle(wxPoint(v_center + dot_dist * idx, v_center), track_rc.height * (double)(idx == m_selection ? 0.32 : 0.16));
}
}
else { // Developer mode
wxString str = "DEV";
int kerning = 3; // pixels between chars
dc.SetTextForeground(text_color.colorForStates(states));
dc.SetPen(wxPen(thumb_border, 1));
dc.SetBrush(wxBrush(thumb_fill));
dc.DrawRoundedRectangle(thumb, thumb.height / 2.0);
wxCoord totalWidth = 0;
for (char c : str)
totalWidth += dc.GetTextExtent(wxString(c)).x + kerning;
totalWidth -= kerning;
wxCoord x = bounds.x + (bounds.width - totalWidth) / 2;
wxCoord y = bounds.y + (bounds.height - dc.GetTextExtent(str).y) / 2 - 1;
for (char c : str) {
wxString ch(c);
dc.DrawText(ch, x, y);
x += dc.GetTextExtent(ch).x + kerning;
}
}
}
void ModeSwitchButton::mouseDown(wxMouseEvent& event)
+6
View File
@@ -78,7 +78,13 @@ private:
private:
int m_selection { 0 };
bool m_pressed { false };
bool m_enabled { true };
wxString m_tooltips[3];
StateColor dot_active;
StateColor dot_dimmed;
StateColor text_color;
StateColor track_background;
StateColor track_border;
};
class MultiSwitchButton : public StaticBox

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