mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-13 02:41:16 +00:00
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:
+21
-3
@@ -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 */
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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,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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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, ¤t_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, ¤t_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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)};
|
||||
}
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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") },
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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())
|
||||
|
||||
@@ -105,6 +105,8 @@ public:
|
||||
|
||||
bool HasDismissLongTime();
|
||||
|
||||
void Popup(wxWindow *focus = nullptr) override;
|
||||
|
||||
protected:
|
||||
void Dismiss() override;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user