From cd4cddfca43697994267a40c4b05e0d2f70c18e2 Mon Sep 17 00:00:00 2001 From: "zhimin.zeng" Date: Tue, 14 Mar 2023 10:42:42 +0800 Subject: [PATCH] ENH: Cut optimization, support for custom connectors Change-Id: I65163314374fb74f0b16df47dacae82caa6fab0d (cherry picked from commit 7bacc2c2a89be471f6fee51dd07a42222a28b55a) --- resources/images/collapse_btn.svg | 12 + resources/images/cut_.svg | 26 + resources/images/cut_connectors.svg | 20 + resources/images/expand_btn.svg | 13 + resources/images/revert_btn.svg | 12 + resources/shaders/gouraud_light_uniform.fs | 14 + resources/shaders/gouraud_light_uniform.vs | 45 + src/imgui/imconfig.h | 4 + src/libslic3r/Format/bbs_3mf.cpp | 157 +- src/libslic3r/Geometry.cpp | 14 + src/libslic3r/Geometry.hpp | 9 + src/libslic3r/Model.cpp | 559 +++++- src/libslic3r/Model.hpp | 149 +- src/libslic3r/ObjectID.hpp | 64 +- src/libslic3r/Point.hpp | 3 + src/libslic3r/TriangleMesh.cpp | 55 + src/libslic3r/TriangleMesh.hpp | 1 + src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/CameraUtils.cpp | 131 ++ src/slic3r/GUI/CameraUtils.hpp | 69 + src/slic3r/GUI/GLCanvas3D.cpp | 10 + src/slic3r/GUI/GLShadersManager.cpp | 4 + src/slic3r/GUI/GUI_Factories.cpp | 15 + src/slic3r/GUI/GUI_Factories.hpp | 2 + src/slic3r/GUI/GUI_ObjectList.cpp | 507 ++++- src/slic3r/GUI/GUI_ObjectList.hpp | 18 +- src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp | 1749 +++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp | 129 +- src/slic3r/GUI/Gizmos/GLGizmoBase.hpp | 2 + src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 10 + src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 10 +- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 19 + src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp | 4 + src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 34 +- src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 3 +- src/slic3r/GUI/ImGuiWrapper.cpp | 35 + src/slic3r/GUI/ImGuiWrapper.hpp | 2 + src/slic3r/GUI/MeshUtils.cpp | 36 + src/slic3r/GUI/MeshUtils.hpp | 18 + src/slic3r/GUI/MsgDialog.cpp | 2 +- src/slic3r/GUI/NotificationManager.cpp | 1 + src/slic3r/GUI/ObjectDataViewModel.cpp | 66 +- src/slic3r/GUI/ObjectDataViewModel.hpp | 12 +- src/slic3r/GUI/Plater.cpp | 59 +- src/slic3r/GUI/Plater.hpp | 2 +- src/slic3r/GUI/Widgets/Button.cpp | 8 +- src/slic3r/GUI/Widgets/Button.hpp | 4 +- 51 files changed, 3663 insertions(+), 466 deletions(-) create mode 100644 resources/images/collapse_btn.svg create mode 100644 resources/images/cut_.svg create mode 100644 resources/images/cut_connectors.svg create mode 100644 resources/images/expand_btn.svg create mode 100644 resources/images/revert_btn.svg create mode 100644 resources/shaders/gouraud_light_uniform.fs create mode 100644 resources/shaders/gouraud_light_uniform.vs create mode 100644 src/slic3r/GUI/CameraUtils.cpp create mode 100644 src/slic3r/GUI/CameraUtils.hpp diff --git a/resources/images/collapse_btn.svg b/resources/images/collapse_btn.svg new file mode 100644 index 000000000..32d7f9959 --- /dev/null +++ b/resources/images/collapse_btn.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/resources/images/cut_.svg b/resources/images/cut_.svg new file mode 100644 index 000000000..0919e3952 --- /dev/null +++ b/resources/images/cut_.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + diff --git a/resources/images/cut_connectors.svg b/resources/images/cut_connectors.svg new file mode 100644 index 000000000..504df0a41 --- /dev/null +++ b/resources/images/cut_connectors.svg @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/resources/images/expand_btn.svg b/resources/images/expand_btn.svg new file mode 100644 index 000000000..4ee221a44 --- /dev/null +++ b/resources/images/expand_btn.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/images/revert_btn.svg b/resources/images/revert_btn.svg new file mode 100644 index 000000000..fbc580d88 --- /dev/null +++ b/resources/images/revert_btn.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/resources/shaders/gouraud_light_uniform.fs b/resources/shaders/gouraud_light_uniform.fs new file mode 100644 index 000000000..342642bcb --- /dev/null +++ b/resources/shaders/gouraud_light_uniform.fs @@ -0,0 +1,14 @@ +#version 140 + +uniform vec4 uniform_color; +uniform float emission_factor; + +// x = tainted, y = specular; +in vec2 intensity; + +out vec4 out_color; + +void main() +{ + out_color = uniform_color; +} diff --git a/resources/shaders/gouraud_light_uniform.vs b/resources/shaders/gouraud_light_uniform.vs new file mode 100644 index 000000000..fad848f8b --- /dev/null +++ b/resources/shaders/gouraud_light_uniform.vs @@ -0,0 +1,45 @@ +#version 140 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat3 view_normal_matrix; + +in vec3 v_position; +in vec3 v_normal; + +// x = tainted, y = specular; +out vec2 intensity; + +void main() +{ + // First transform the normal into camera space and normalize the result. + vec3 normal = normalize(view_normal_matrix * v_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec4 position = view_model_matrix * vec4(v_position, 1.0); + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + gl_Position = projection_matrix * position; +} diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index 51aa772cd..cdb6275ce 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -189,6 +189,10 @@ namespace ImGui const wchar_t TextSearchIcon = 0x0828; const wchar_t TextSearchCloseIcon = 0x0829; + const wchar_t ExpandBtn = 0x0830; + const wchar_t CollapseBtn = 0x0831; + const wchar_t RevertBtn = 0x0832; + // void MyFunction(const char* name, const MyMatrix44& v); } diff --git a/src/libslic3r/Format/bbs_3mf.cpp b/src/libslic3r/Format/bbs_3mf.cpp index 21b6a608e..eb6bbe8ad 100644 --- a/src/libslic3r/Format/bbs_3mf.cpp +++ b/src/libslic3r/Format/bbs_3mf.cpp @@ -163,7 +163,7 @@ const std::string PROJECT_EMBEDDED_PRINT_PRESETS_FILE = "Metadata/print_setting_ const std::string PROJECT_EMBEDDED_SLICE_PRESETS_FILE = "Metadata/process_settings_"; const std::string PROJECT_EMBEDDED_FILAMENT_PRESETS_FILE = "Metadata/filament_settings_"; const std::string PROJECT_EMBEDDED_PRINTER_PRESETS_FILE = "Metadata/machine_settings_"; - +const std::string CUT_INFORMATION_FILE = "Metadata/cut_information.xml"; const unsigned int AUXILIARY_STR_LEN = 12; const unsigned int METADATA_STR_LEN = 9; @@ -680,6 +680,19 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) VolumeMetadataList volumes; }; + struct CutObjectInfo + { + struct Connector + { + int volume_id; + int type; + float r_tolerance; + float h_tolerance; + }; + CutObjectBase id; + std::vector connectors; + }; + // Map from a 1 based 3MF object ID to a 0 based ModelObject index inside m_model->objects. //typedef std::pair Id; // BBS: encrypt typedef std::map IdToCurrentObjectMap; @@ -688,6 +701,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) //typedef std::map IdToAliasesMap; typedef std::vector InstancesList; typedef std::map IdToMetadataMap; + typedef std::map IdToCutObjectInfoMap; //typedef std::map IdToGeometryMap; typedef std::map> IdToLayerHeightsProfileMap; /*typedef std::map IdToLayerConfigRangesMap; @@ -879,6 +893,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) //IdToGeometryMap m_orig_geometries; // backup & restore CurrentConfig m_curr_config; IdToMetadataMap m_objects_metadata; + IdToCutObjectInfoMap m_cut_object_infos; IdToLayerHeightsProfileMap m_layer_heights_profiles; /*IdToLayerConfigRangesMap m_layer_config_ranges; IdToSlaSupportPointsMap m_sla_support_points; @@ -937,6 +952,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) bool _extract_xml_from_archive(mz_zip_archive& archive, std::string const & path, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler); bool _extract_xml_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler); bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_cut_information_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, ConfigSubstitutionContext &config_substitutions); void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions); void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); @@ -1466,6 +1482,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) // extract slic3r print config file _extract_project_config_from_archive(archive, stat, config, config_substitutions, model); } + else if (boost::algorithm::iequals(name, CUT_INFORMATION_FILE)) { + // extract object cut info + _extract_cut_information_from_archive(archive, stat, config_substitutions); + } //BBS: project embedded presets else if (!dont_load_config && boost::algorithm::istarts_with(name, PROJECT_EMBEDDED_PRINT_PRESETS_FILE)) { // extract slic3r layer config ranges file @@ -1689,6 +1709,19 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) if (!_generate_volumes_new(*model_object, object_id_list, *volumes_ptr, config_substitutions)) return false; + + // Apply cut information for object if any was loaded + // m_cut_object_ids are indexed by a 1 based model object index. + IdToCutObjectInfoMap::iterator cut_object_info = m_cut_object_infos.find(object.second + 1); + if (cut_object_info != m_cut_object_infos.end()) { + model_object->cut_id = cut_object_info->second.id; + + for (auto connector : cut_object_info->second.connectors) { + assert(0 <= connector.volume_id && connector.volume_id <= int(model_object->volumes.size())); + model_object->volumes[connector.volume_id]->cut_info = + ModelVolume::CutInfo(CutConnectorType(connector.type), connector.r_tolerance, connector.h_tolerance, true); + } + } } // If instances contain a single volume, the volume offset should be 0,0,0 @@ -2045,6 +2078,61 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) return true; } + void _BBS_3MF_Importer::_extract_cut_information_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, ConfigSubstitutionContext &config_substitutions) + { + if (stat.m_uncomp_size > 0) { + std::string buffer((size_t) stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void *) buffer.data(), (size_t) stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading cut information data to buffer"); + return; + } + + std::istringstream iss(buffer); // wrap returned xml to istringstream + pt::ptree objects_tree; + pt::read_xml(iss, objects_tree); + + for (const auto &object : objects_tree.get_child("objects")) { + pt::ptree object_tree = object.second; + int obj_idx = object_tree.get(".id", -1); + if (obj_idx <= 0) { + add_error("Found invalid object id"); + continue; + } + + IdToCutObjectInfoMap::iterator object_item = m_cut_object_infos.find(obj_idx); + if (object_item != m_cut_object_infos.end()) { + add_error("Found duplicated cut_object_id"); + continue; + } + + CutObjectBase cut_id; + std::vector connectors; + + for (const auto &obj_cut_info : object_tree) { + if (obj_cut_info.first == "cut_id") { + pt::ptree cut_id_tree = obj_cut_info.second; + cut_id = CutObjectBase(ObjectID(cut_id_tree.get(".id")), cut_id_tree.get(".check_sum"), + cut_id_tree.get(".connectors_cnt")); + } + if (obj_cut_info.first == "connectors") { + pt::ptree cut_connectors_tree = obj_cut_info.second; + for (const auto &cut_connector : cut_connectors_tree) { + if (cut_connector.first != "connector") continue; + pt::ptree connector_tree = cut_connector.second; + CutObjectInfo::Connector connector = {connector_tree.get(".volume_id"), connector_tree.get(".type"), + connector_tree.get(".r_tolerance"), connector_tree.get(".h_tolerance")}; + connectors.emplace_back(connector); + } + } + } + + CutObjectInfo cut_info{cut_id, connectors}; + m_cut_object_infos.insert({obj_idx, cut_info}); + } + } + } + void _BBS_3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, const std::string& archive_filename) { if (stat.m_uncomp_size > 0) { @@ -4813,6 +4901,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) //BBS: add project embedded preset files bool _add_project_embedded_presets_to_archive(mz_zip_archive& archive, Model& model, std::vector project_presets); bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const IdToObjectDataMap &objects_data, int export_plate_idx = -1, bool save_gcode = true, bool use_loaded_id = false); + bool _add_cut_information_file_to_archive(mz_zip_archive &archive, Model &model); bool _add_slice_info_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list); bool _add_gcode_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, Export3mfProgressFn proFn = nullptr); bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config); @@ -5283,6 +5372,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) return false; } + if (!_add_cut_information_file_to_archive(archive, model)) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", _add_cut_information_file_to_archive failed\n"); + return false; + } + //BBS progress point BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", before add sliced info to 3mf\n"); if (proFn) { @@ -6699,6 +6793,67 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) return true; } + bool _BBS_3MF_Exporter::_add_cut_information_file_to_archive(mz_zip_archive &archive, Model &model) + { + std::string out = ""; + pt::ptree tree; + + unsigned int object_cnt = 0; + for (const ModelObject *object : model.objects) { + object_cnt++; + pt::ptree &obj_tree = tree.add("objects.object", ""); + + obj_tree.put(".id", object_cnt); + + // Store info for cut_id + pt::ptree &cut_id_tree = obj_tree.add("cut_id", ""); + + // store cut_id atributes + cut_id_tree.put(".id", object->cut_id.id().id); + cut_id_tree.put(".check_sum", object->cut_id.check_sum()); + cut_id_tree.put(".connectors_cnt", object->cut_id.connectors_cnt()); + + int volume_idx = -1; + for (const ModelVolume *volume : object->volumes) { + ++volume_idx; + if (volume->is_cut_connector()) { + pt::ptree &connectors_tree = obj_tree.add("connectors.connector", ""); + connectors_tree.put(".volume_id", volume_idx); + connectors_tree.put(".type", int(volume->cut_info.connector_type)); + connectors_tree.put(".r_tolerance", volume->cut_info.radius_tolerance); + connectors_tree.put(".h_tolerance", volume->cut_info.height_tolerance); + } + } + } + + if (!tree.empty()) { + std::ostringstream oss; + pt::write_xml(oss, tree); + out = oss.str(); + + // Post processing("beautification") of the output string for a better preview + boost::replace_all(out, ">\n \n ", ">\n "); + boost::replace_all(out, ">\n ", ">\n "); + boost::replace_all(out, ">\n ", ">\n "); + boost::replace_all(out, ">", ">\n "); + // OR just + boost::replace_all(out, "><", ">\n<"); + } + + if (!out.empty()) { + if (!mz_zip_writer_add_mem(&archive, CUT_INFORMATION_FILE.c_str(), (const void *) out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add cut information file to archive"); + return false; + } + } + + return true; + } + bool _BBS_3MF_Exporter::_add_slice_info_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list) { std::stringstream stream; diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index ca92c241e..a2c59ec17 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -409,6 +409,20 @@ void rotation_from_two_vectors(Vec3d from, Vec3d to, Vec3d& rotation_axis, doubl } } +Transform3d translation_transform(const Vec3d &translation) +{ + Transform3d transform = Transform3d::Identity(); + transform.translate(translation); + return transform; +} + +Transform3d rotation_transform(const Vec3d& rotation) +{ + Transform3d transform = Transform3d::Identity(); + transform.rotate(Eigen::AngleAxisd(rotation.z(), Vec3d::UnitZ()) * Eigen::AngleAxisd(rotation.y(), Vec3d::UnitY()) * Eigen::AngleAxisd(rotation.x(), Vec3d::UnitX())); + return transform; +} + Transformation::Flags::Flags() : dont_translate(true) , dont_rotate(true) diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index fde3815ea..8eb6195a1 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -348,6 +348,15 @@ Vec3d extract_euler_angles(const Transform3d& transform); // Euler angles can be obtained by extract_euler_angles() void rotation_from_two_vectors(Vec3d from, Vec3d to, Vec3d& rotation_axis, double& phi, Matrix3d* rotation_matrix = nullptr); +// Returns the transform obtained by assembling the given translation +Transform3d translation_transform(const Vec3d &translation); + +// Returns the transform obtained by assembling the given rotations in the following order: +// 1) rotate X +// 2) rotate Y +// 3) rotate Z +Transform3d rotation_transform(const Vec3d &rotation); + class Transformation { struct Flags diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 8fd3d3ce8..a9df179dc 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -670,8 +670,21 @@ bool Model::looks_like_imperial_units() const return false; for (ModelObject* obj : this->objects) - if (obj->get_object_stl_stats().volume < volume_threshold_inches) - return true; + if (obj->get_object_stl_stats().volume < volume_threshold_inches) { + if (!obj->is_cut()) + return true; + bool all_cut_parts_look_like_imperial_units = true; + for (ModelObject* obj_other : this->objects) { + if (obj_other == obj) + continue; + if (obj_other->cut_id.is_equal(obj->cut_id) && obj_other->get_object_stl_stats().volume >= volume_threshold_inches) { + all_cut_parts_look_like_imperial_units = false; + break; + } + } + if (all_cut_parts_look_like_imperial_units) + return true; + } return false; } @@ -930,6 +943,7 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs) this->layer_height_profile = rhs.layer_height_profile; this->printable = rhs.printable; this->origin_translation = rhs.origin_translation; + this->cut_id.copy(rhs.cut_id); m_bounding_box = rhs.m_bounding_box; m_bounding_box_valid = rhs.m_bounding_box_valid; m_raw_bounding_box = rhs.m_raw_bounding_box; @@ -1042,6 +1056,9 @@ ModelVolume* ModelObject::add_volume(const ModelVolume &other, ModelVolumeType t ModelVolume* v = new ModelVolume(this, other); if (type != ModelVolumeType::INVALID && v->type() != type) v->set_type(type); + + v->cut_info = other.cut_info; + this->volumes.push_back(v); // The volume should already be centered at this point of time when copying shared pointers of the triangle mesh and convex hull. // v->center_geometry_after_creation(); @@ -1591,6 +1608,344 @@ size_t ModelObject::parts_count() const return num; } +bool ModelObject::has_connectors() const +{ + assert(is_cut()); + for (const ModelVolume *v : this->volumes) + if (v->cut_info.is_connector) return true; + + return false; +} + +indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes connector_attributes) +{ + indexed_triangle_set connector_mesh; + + int sectorCount {1}; + switch (CutConnectorShape(connector_attributes.shape)) { + case CutConnectorShape::Triangle: + sectorCount = 3; + break; + case CutConnectorShape::Square: + sectorCount = 4; + break; + case CutConnectorShape::Circle: + sectorCount = 360; + break; + case CutConnectorShape::Hexagon: + sectorCount = 6; + break; + default: + break; + } + + if (connector_attributes.style == CutConnectorStyle::Prizm) + connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount)); + else if (connector_attributes.type == CutConnectorType::Plug) + connector_mesh = its_make_cone(1.0, 1.0, (2 * PI / sectorCount)); + else + connector_mesh = its_make_frustum_dowel(1.0, 1.0, sectorCount); + + return connector_mesh; +} + +void ModelObject::apply_cut_connectors(const std::string &name) +{ + if (cut_connectors.empty()) + return; + + using namespace Geometry; + + size_t connector_id = cut_id.connectors_cnt(); + for (const CutConnector &connector : cut_connectors) { + TriangleMesh mesh = TriangleMesh(get_connector_mesh(connector.attribs)); + // Mesh will be centered when loading. + ModelVolume *new_volume = add_volume(std::move(mesh), ModelVolumeType::NEGATIVE_VOLUME); + + Transform3d translate_transform = Transform3d::Identity(); + translate_transform.translate(connector.pos); + Transform3d scale_transform = Transform3d::Identity(); + scale_transform.scale(Vec3f(connector.radius, connector.radius, connector.height).cast()); + + // Transform the new modifier to be aligned inside the instance + new_volume->set_transformation(translate_transform * connector.rotation_m * scale_transform); + + new_volume->cut_info = {connector.attribs.type, connector.radius_tolerance, connector.height_tolerance}; + new_volume->name = name + "-" + std::to_string(++connector_id); + } + cut_id.increase_connectors_cnt(cut_connectors.size()); + + // delete all connectors + cut_connectors.clear(); +} + +void ModelObject::invalidate_cut() +{ + this->cut_id.invalidate(); + for (ModelVolume *volume : this->volumes) + volume->invalidate_cut_info(); +} + +void ModelObject::delete_connectors() +{ + for (int id = int(this->volumes.size()) - 1; id >= 0; id--) { + if (volumes[id]->is_cut_connector()) + this->delete_volume(size_t(id)); + } +} + +void ModelObject::synchronize_model_after_cut() +{ + for (ModelObject *obj : m_model->objects) { + if (obj == this || obj->cut_id.is_equal(this->cut_id)) continue; + if (obj->is_cut() && obj->cut_id.has_same_id(this->cut_id)) + obj->cut_id.copy(this->cut_id); + } +} + +void ModelObject::apply_cut_attributes(ModelObjectCutAttributes attributes) +{ + // we don't save cut information, if result will not contains all parts of initial object + if (!attributes.has(ModelObjectCutAttribute::KeepUpper) || !attributes.has(ModelObjectCutAttribute::KeepLower)) + return; + + if (cut_id.id().invalid()) + cut_id.init(); + + { + int cut_obj_cnt = -1; + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) + cut_obj_cnt++; + if (attributes.has(ModelObjectCutAttribute::KeepLower)) + cut_obj_cnt++; + if (attributes.has(ModelObjectCutAttribute::CreateDowels)) + cut_obj_cnt++; + if (cut_obj_cnt > 0) + cut_id.increase_check_sum(size_t(cut_obj_cnt)); + } +} + +void ModelObject::clone_for_cut(ModelObject **obj) +{ + (*obj) = ModelObject::new_clone(*this); + (*obj)->set_model(nullptr); + (*obj)->sla_support_points.clear(); + (*obj)->sla_drain_holes.clear(); + (*obj)->sla_points_status = sla::PointsStatus::NoPoints; + (*obj)->clear_volumes(); + (*obj)->input_file.clear(); +} + +Transform3d ModelObject::calculate_cut_plane_inverse_matrix(const std::array& plane_points) +{ + Vec3d mid_point = {0.0, 0.0, 0.0}; + for (auto pt : plane_points) + mid_point += pt; + mid_point /= (double) plane_points.size(); + + Vec3d movement = -mid_point; + + Vec3d v01 = plane_points[1] - plane_points[0]; + Vec3d v12 = plane_points[2] - plane_points[1]; + + Vec3d plane_normal = v01.cross(v12); + plane_normal.normalize(); + + Vec3d axis = {0.0, 0.0, 0.0}; + double phi = 0.0; + Matrix3d matrix; + matrix.setIdentity(); + Geometry::rotation_from_two_vectors(plane_normal, {0.0, 0.0, 1.0}, axis, phi, &matrix); + Vec3d angles = Geometry::extract_euler_angles(matrix); + + movement = matrix * movement; + Transform3d transfo; + transfo.setIdentity(); + transfo.translate(movement); + transfo.rotate(Eigen::AngleAxisd(angles(2), Vec3d::UnitZ()) * Eigen::AngleAxisd(angles(1), Vec3d::UnitY()) * Eigen::AngleAxisd(angles(0), Vec3d::UnitX())); + return transfo; +} + +void ModelObject::process_connector_cut( + ModelVolume *volume, + ModelObjectCutAttributes attributes, + ModelObject *upper, ModelObject *lower, + std::vector &dowels, + Vec3d &local_dowels_displace) +{ + assert(volume->cut_info.is_connector); + volume->cut_info.set_processed(); + + const auto volume_matrix = volume->get_matrix(); + + // ! Don't apply instance transformation for the conntectors. + // This transformation is already there + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { + ModelVolume *vol = upper->add_volume(*volume); + vol->set_transformation(volume_matrix); + vol->apply_tolerance(); + } + if (attributes.has(ModelObjectCutAttribute::KeepLower)) { + ModelVolume *vol = lower->add_volume(*volume); + vol->set_transformation(volume_matrix); + + if (volume->cut_info.connector_type == CutConnectorType::Dowel) + vol->apply_tolerance(); + else + // for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug + vol->set_type(ModelVolumeType::MODEL_PART); + } + if (volume->cut_info.connector_type == CutConnectorType::Dowel && attributes.has(ModelObjectCutAttribute::CreateDowels)) { + ModelObject *dowel{nullptr}; + // Clone the object to duplicate instances, materials etc. + clone_for_cut(&dowel); + + // add one more solid part same as connector if this connector is a dowel + ModelVolume *vol = dowel->add_volume(*volume); + vol->set_type(ModelVolumeType::MODEL_PART); + + // But discard rotation and Z-offset for this volume + vol->set_rotation(Vec3d::Zero()); + vol->set_offset(Z, 0.0); + + // Compute the displacement (in instance coordinates) to be applied to place the dowels + local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 0.0)); + + dowels.push_back(dowel); + } +} + +void ModelObject::process_modifier_cut( + ModelVolume *volume, + const Transform3d &instance_matrix, + const Transform3d &inverse_cut_matrix, + ModelObjectCutAttributes attributes, + ModelObject *upper, + ModelObject *lower) +{ + const auto volume_matrix = instance_matrix * volume->get_matrix(); + + // Modifiers are not cut, but we still need to add the instance transformation + // to the modifier volume transformation to preserve their shape properly. + volume->set_transformation(Geometry::Transformation(volume_matrix)); + + // Some logic for the negative volumes/connectors. Add only needed modifiers + auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix); + bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0; + if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut)) + upper->add_volume(*volume); + if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut)) + lower->add_volume(*volume); +} + +void ModelObject::process_solid_part_cut(ModelVolume * volume, + const Transform3d & instance_matrix, + const std::array &plane_points, + ModelObjectCutAttributes attributes, + ModelObject * upper, + ModelObject * lower, + Vec3d & local_displace) +{ + // Transform the mesh by the combined transformation matrix. + // Flip the triangles in case the composite transformation is left handed. + TriangleMesh mesh(volume->mesh()); + mesh.transform(instance_matrix * volume->get_matrix(), true); + volume->reset_mesh(); + // Reset volume transformation except for offset + const Vec3d offset = volume->get_offset(); + volume->set_transformation(Geometry::Transformation()); + volume->set_offset(offset); + + // Perform cut + TriangleMesh upper_mesh, lower_mesh; + { + indexed_triangle_set upper_its, lower_its; + cut_mesh(mesh.its, plane_points, &upper_its, &lower_its); + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) upper_mesh = TriangleMesh(upper_its); + if (attributes.has(ModelObjectCutAttribute::KeepLower)) lower_mesh = TriangleMesh(lower_its); + } + + if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper_mesh.empty()) { + ModelVolume *vol = upper->add_volume(upper_mesh); + vol->name = volume->name.substr(0, volume->name.find_last_of('.')) + "_upper"; // BBS + // Don't copy the config's ID. + vol->config.assign_config(volume->config); + assert(vol->config.id().valid()); + assert(vol->config.id() != volume->config.id()); + vol->set_material(volume->material_id(), *volume->material()); + vol->cut_info = volume->cut_info; + } + if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) { + ModelVolume *vol = lower->add_volume(lower_mesh); + vol->name = volume->name.substr(0, volume->name.find_last_of('.')) + "_lower"; // BBS + // Don't copy the config's ID. + vol->config.assign_config(volume->config); + assert(vol->config.id().valid()); + assert(vol->config.id() != volume->config.id()); + vol->set_material(volume->material_id(), *volume->material()); + vol->cut_info = volume->cut_info; + + // Compute the displacement (in instance coordinates) to be applied to place the upper parts + // The upper part displacement is set to half of the lower part bounding box + // this is done in hope at least a part of the upper part will always be visible and draggable + local_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0)); + } +} + +static void invalidate_translations(ModelObject* object, const ModelInstance* src_instance) +{ + if (!object->origin_translation.isApprox(Vec3d::Zero()) && src_instance->get_offset().isApprox(Vec3d::Zero())) { + object->center_around_origin(); + object->translate_instances(-object->origin_translation); + object->origin_translation = Vec3d::Zero(); + } + else { + object->invalidate_bounding_box(); + object->center_around_origin(); + } +} + +static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix, + bool place_on_cut = false, bool flip = false, Vec3d local_displace = Vec3d::Zero()) +{ + using namespace Geometry; + + // Reset instance transformation except offset and Z-rotation + + for (size_t i = 0; i < object->instances.size(); ++i) { + auto& obj_instance = object->instances[i]; + const Vec3d offset = obj_instance->get_offset(); + const double rot_z = obj_instance->get_rotation().z(); + + obj_instance->set_transformation(Transformation()); + + const Vec3d displace = local_displace.isApprox(Vec3d::Zero()) ? Vec3d::Zero() : + rotation_transform(obj_instance->get_rotation()) * local_displace; + obj_instance->set_offset(offset + displace); + + Vec3d rotation = Vec3d::Zero(); + if (!flip && !place_on_cut) { + if ( i != src_instance_idx) + rotation[Z] = rot_z; + } + else { + Transform3d rotation_matrix = Transform3d::Identity(); + if (flip) + rotation_matrix = rotation_transform(PI * Vec3d::UnitX()); + + if (place_on_cut) + rotation_matrix = rotation_matrix * Transformation(cut_matrix).get_matrix(true, false, true, true).inverse(); + + if (i != src_instance_idx) + rotation_matrix = rotation_transform(rot_z * Vec3d::UnitZ()) * rotation_matrix; + + rotation = Transformation(rotation_matrix).get_rotation(); + } + + obj_instance->set_rotation(rotation); + } +} + // BBS: replace z with plane_points ModelObjectPtrs ModelObject::cut(size_t instance, std::array plane_points, ModelObjectCutAttributes attributes) { @@ -1599,12 +1954,14 @@ ModelObjectPtrs ModelObject::cut(size_t instance, std::array plane_poi BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start"; + // apply cut attributes for object + apply_cut_attributes(attributes); + // Clone the object to duplicate instances, materials etc. bool keep_upper = attributes.has(ModelObjectCutAttribute::KeepUpper); bool keep_lower = attributes.has(ModelObjectCutAttribute::KeepLower); - bool cut_to_parts = attributes.has(ModelObjectCutAttribute::CutToParts); ModelObject* upper = keep_upper ? ModelObject::new_clone(*this) : nullptr; - ModelObject* lower = (cut_to_parts&&upper!=nullptr) ? upper : (keep_lower ? ModelObject::new_clone(*this) : nullptr); + ModelObject* lower = keep_lower ? ModelObject::new_clone(*this) : nullptr; if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { upper->set_model(nullptr); @@ -1643,8 +2000,10 @@ ModelObjectPtrs ModelObject::cut(size_t instance, std::array plane_poi point -= instances[instance]->get_offset(); } + std::vector dowels; // Displacement (in instance coordinates) to be applied to place the upper parts Vec3d local_displace = Vec3d::Zero(); + Vec3d local_dowels_displace = Vec3d::Zero(); for (ModelVolume *volume : volumes) { const auto volume_matrix = volume->get_matrix(); @@ -1654,121 +2013,106 @@ ModelObjectPtrs ModelObject::cut(size_t instance, std::array plane_poi volume->mmu_segmentation_facets.reset(); if (! volume->is_model_part()) { - // Modifiers are not cut, but we still need to add the instance transformation - // to the modifier volume transformation to preserve their shape properly. - - volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix)); - - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) - upper->add_volume(*volume); - if (attributes.has(ModelObjectCutAttribute::KeepLower)) - lower->add_volume(*volume); + if (volume->cut_info.is_processed) { + // Modifiers are not cut, but we still need to add the instance transformation + // to the modifier volume transformation to preserve their shape properly. + Transform3d inverse_cut_matrix = calculate_cut_plane_inverse_matrix(plane_points); + process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower); + } + else { + process_connector_cut(volume, attributes, upper, lower, dowels, local_dowels_displace); + } } else if (! volume->mesh().empty()) { - // Transform the mesh by the combined transformation matrix. - // Flip the triangles in case the composite transformation is left handed. - TriangleMesh mesh(volume->mesh()); - mesh.transform(instance_matrix * volume_matrix, true); - volume->reset_mesh(); - // Reset volume transformation except for offset - const Vec3d offset = volume->get_offset(); - volume->set_transformation(Geometry::Transformation()); - volume->set_offset(offset); - - // Perform cut - TriangleMesh upper_mesh, lower_mesh; - { - indexed_triangle_set upper_its, lower_its; - cut_mesh(mesh.its, plane_points, &upper_its, &lower_its); - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) - upper_mesh = TriangleMesh(upper_its); - if (attributes.has(ModelObjectCutAttribute::KeepLower)) - lower_mesh = TriangleMesh(lower_its); - } - - if (attributes.has(ModelObjectCutAttribute::KeepUpper) && ! upper_mesh.empty()) { - ModelVolume* vol = upper->add_volume(upper_mesh); - vol->name = volume->name.substr(0, volume->name.find_last_of('.')) + "_upper"; // BBS - // Don't copy the config's ID. - vol->config.assign_config(volume->config); - assert(vol->config.id().valid()); - assert(vol->config.id() != volume->config.id()); - vol->set_material(volume->material_id(), *volume->material()); - } - if (attributes.has(ModelObjectCutAttribute::KeepLower) && ! lower_mesh.empty()) { - ModelVolume* vol = lower->add_volume(lower_mesh); - vol->name = volume->name.substr(0, volume->name.find_last_of('.')) + "_lower"; // BBS - // Don't copy the config's ID. - vol->config.assign_config(volume->config); - assert(vol->config.id().valid()); - assert(vol->config.id() != volume->config.id()); - vol->set_material(volume->material_id(), *volume->material()); - - // Compute the displacement (in instance coordinates) to be applied to place the upper parts - // The upper part displacement is set to half of the lower part bounding box - // this is done in hope at least a part of the upper part will always be visible and draggable - local_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0)); - } + process_solid_part_cut(volume, instance_matrix, plane_points, attributes, upper, lower, local_displace); } } ModelObjectPtrs res; + Transform3d cut_matrix = calculate_cut_plane_inverse_matrix(plane_points).inverse(); if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper->volumes.size() > 0) { - if (!upper->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) { - // BBS: do not move the parts if cut_to_parts - if (!cut_to_parts) { - upper->center_around_origin(); - upper->translate_instances(-upper->origin_translation); - upper->origin_translation = Vec3d::Zero(); - } - } + invalidate_translations(upper, instances[instance]); - // Reset instance transformation except offset and Z-rotation - for (size_t i = 0; i < instances.size(); ++i) { - auto &instance = upper->instances[i]; - const Vec3d offset = instance->get_offset(); - // BBS - //const double rot_z = instance->get_rotation().z(); - // BBS: do not move the parts if cut_to_parts - Vec3d displace(0, 0, 0); - if (!cut_to_parts) - displace = Geometry::assemble_transform(Vec3d::Zero(), instance->get_rotation()) * local_displace; - - instance->set_transformation(Geometry::Transformation()); - instance->set_offset(offset + displace); - // BBS - //instance->set_rotation(Vec3d(0.0, 0.0, rot_z)); - } + reset_instance_transformation(upper, instance, cut_matrix, + attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), + attributes.has(ModelObjectCutAttribute::FlipUpper), + local_displace); res.push_back(upper); } if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower->volumes.size() > 0) { - if (!lower->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) { - if (!cut_to_parts) { - lower->center_around_origin(); - lower->translate_instances(-lower->origin_translation); - lower->origin_translation = Vec3d::Zero(); + invalidate_translations(lower, instances[instance]); + + reset_instance_transformation(lower, instance, cut_matrix, + attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), + attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) ? true : attributes.has(ModelObjectCutAttribute::FlipLower)); + + res.push_back(lower); + } + + if (attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) { + auto invalidate_translations = [](ModelObject *object, const ModelInstance *src_instance) { + if (!object->origin_translation.isApprox(Vec3d::Zero()) && src_instance->get_offset().isApprox(Vec3d::Zero())) { + object->center_around_origin(); + object->translate_instances(-object->origin_translation); + object->origin_translation = Vec3d::Zero(); + } else { + object->invalidate_bounding_box(); + object->center_around_origin(); } - } + }; - // Reset instance transformation except offset and Z-rotation - for (auto *instance : lower->instances) { - const Vec3d offset = instance->get_offset(); - // BBS - //const double rot_z = instance->get_rotation().z(); - instance->set_transformation(Geometry::Transformation()); - instance->set_offset(offset); - // BBS - //instance->set_rotation(Vec3d(attributes.has(ModelObjectCutAttribute::FlipLower) ? Geometry::deg2rad(180.0) : 0.0, 0.0, rot_z)); - } + auto reset_instance_transformation = [](ModelObject *object, size_t src_instance_idx, const Transform3d &cut_matrix, + bool place_on_cut = false, bool flip = false, Vec3d local_displace = Vec3d::Zero()) { + using namespace Geometry; + // Reset instance transformation except offset and Z-rotation + for (size_t i = 0; i < object->instances.size(); ++i) { + auto & obj_instance = object->instances[i]; + const Vec3d offset = obj_instance->get_offset(); + const double rot_z = obj_instance->get_rotation().z(); - if(res.empty() || lower != res.back()) - res.push_back(lower); + obj_instance->set_transformation(Transformation()); + + const Vec3d displace = local_displace.isApprox(Vec3d::Zero()) ? Vec3d::Zero() : rotation_transform(obj_instance->get_rotation()) * local_displace; + obj_instance->set_offset(offset + displace); + + Vec3d rotation = Vec3d::Zero(); + if (!flip && !place_on_cut) { + if (i != src_instance_idx) rotation[Z] = rot_z; + } else { + Transform3d rotation_matrix = Transform3d::Identity(); + if (flip) + rotation_matrix = rotation_transform(PI * Vec3d::UnitX()); + + if (place_on_cut) + rotation_matrix = rotation_matrix * Transformation(cut_matrix).get_matrix(true, false, true, true).inverse(); + + if (i != src_instance_idx) + rotation_matrix = rotation_transform(rot_z * Vec3d::UnitZ()) * rotation_matrix; + + rotation = Transformation(rotation_matrix).get_rotation(); + } + + obj_instance->set_rotation(rotation); + } + }; + + for (auto dowel : dowels) { + invalidate_translations(dowel, instances[instance]); + + reset_instance_transformation(dowel, instance, Transform3d::Identity(), false, false, local_dowels_displace); + + local_dowels_displace += dowel->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-1.5, -1.5, 0.0)); + dowel->name += "-Dowel-" + dowel->volumes[0]->name; + res.push_back(dowel); + } } BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end"; + synchronize_model_after_cut(); + return res; } @@ -2386,6 +2730,23 @@ bool ModelVolume::is_splittable() const return m_is_splittable == 1; } +void ModelVolume::apply_tolerance() +{ + assert(cut_info.is_connector); + if (cut_info.is_processed) + return; + + Vec3d sf = get_scaling_factor(); + // make a "hole" wider + sf[X] *= 1. + double(cut_info.radius_tolerance); + sf[Y] *= 1. + double(cut_info.radius_tolerance); + + // make a "hole" dipper + sf[Z] *= 1. + double(cut_info.height_tolerance); + + set_scaling_factor(sf); +} + // BBS std::vector ModelVolume::get_extruders() const { diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index e260c200f..918801701 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -232,6 +232,80 @@ private: friend class ModelObject; }; +enum class CutConnectorType : int { + Plug, + Dowel, + Undef +}; + +enum class CutConnectorStyle : int { + Prizm, + Frustum, + Undef + //,Claw +}; + +enum class CutConnectorShape : int { + Triangle, + Square, + Hexagon, + Circle, + Undef + //,D-shape +}; + +struct CutConnectorAttributes +{ + CutConnectorType type{CutConnectorType::Plug}; + CutConnectorStyle style{CutConnectorStyle::Prizm}; + CutConnectorShape shape{CutConnectorShape::Circle}; + + CutConnectorAttributes() {} + + CutConnectorAttributes(CutConnectorType t, CutConnectorStyle st, CutConnectorShape sh) : type(t), style(st), shape(sh) {} + + CutConnectorAttributes(const CutConnectorAttributes &rhs) : CutConnectorAttributes(rhs.type, rhs.style, rhs.shape) {} + + bool operator==(const CutConnectorAttributes &other) const; + + bool operator!=(const CutConnectorAttributes &other) const { return !(other == (*this)); } + + bool operator<(const CutConnectorAttributes &other) const + { + return this->type < other.type || (this->type == other.type && this->style < other.style) || + (this->type == other.type && this->style == other.style && this->shape < other.shape); + } + + template inline void serialize(Archive &ar) { ar(type, style, shape); } +}; + +struct CutConnector +{ + Vec3d pos; + Transform3d rotation_m; + float radius; + float height; + float radius_tolerance; // [0.f : 1.f] + float height_tolerance; // [0.f : 1.f] + CutConnectorAttributes attribs; + + CutConnector() : pos(Vec3d::Zero()), rotation_m(Transform3d::Identity()), radius(5.f), height(10.f), radius_tolerance(0.f), height_tolerance(0.1f) {} + + CutConnector(Vec3d p, Transform3d rot, float r, float h, float rt, float ht, CutConnectorAttributes attributes) + : pos(p), rotation_m(rot), radius(r), height(h), radius_tolerance(rt), height_tolerance(ht), attribs(attributes) + {} + + CutConnector(const CutConnector &rhs) : CutConnector(rhs.pos, rhs.rotation_m, rhs.radius, rhs.height, rhs.radius_tolerance, rhs.height_tolerance, rhs.attribs) {} + + bool operator==(const CutConnector &other) const; + + bool operator!=(const CutConnector &other) const { return !(other == (*this)); } + + template inline void serialize(Archive &ar) { ar(pos, rotation_m, radius, height, radius_tolerance, height_tolerance, attribs); } +}; + +using CutConnectors = std::vector; + // Declared outside of ModelVolume, so it could be forward declared. enum class ModelVolumeType : int { INVALID = -1, @@ -242,7 +316,7 @@ enum class ModelVolumeType : int { SUPPORT_ENFORCER }; -enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipLower, CutToParts }; +enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels }; using ModelObjectCutAttributes = enum_bitmask; ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute); @@ -293,6 +367,10 @@ public: // BBS: save for compare with new load volumes std::vector volume_ids; + // Connectors to be added into the object before cut and are used to create a solid/negative volumes during a cut perform + CutConnectors cut_connectors; + CutObjectBase cut_id; + Model* get_model() { return m_model; } const Model* get_model() const { return m_model; } // BBS: production extension @@ -383,6 +461,38 @@ public: size_t materials_count() const; size_t facets_count() const; size_t parts_count() const; + + bool is_cut() const { return cut_id.id().valid(); } + bool has_connectors() const; + static indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes); + void apply_cut_connectors(const std::string &name); + // invalidate cut state for this object and its connectors/volumes + void invalidate_cut(); + // delete volumes which are marked as connector for this object + void delete_connectors(); + void synchronize_model_after_cut(); + void apply_cut_attributes(ModelObjectCutAttributes attributes); + void clone_for_cut(ModelObject **obj); + Transform3d calculate_cut_plane_inverse_matrix(const std::array &plane_points); + void process_connector_cut(ModelVolume *volume, + ModelObjectCutAttributes attributes, + ModelObject *upper, ModelObject *lower, + std::vector &dowels, + Vec3d &local_dowels_displace); + void process_modifier_cut(ModelVolume * volume, + const Transform3d & instance_matrix, + const Transform3d & inverse_cut_matrix, + ModelObjectCutAttributes attributes, + ModelObject * upper, + ModelObject * lower); + void process_solid_part_cut(ModelVolume * volume, + const Transform3d & instance_matrix, + const std::array &plane_points, + ModelObjectCutAttributes attributes, + ModelObject * upper, + ModelObject * lower, + Vec3d & local_displace); + // BBS: replace z with plane_points ModelObjectPtrs cut(size_t instance, std::array plane_points, ModelObjectCutAttributes attributes); // BBS @@ -531,7 +641,8 @@ private: Internal::StaticSerializationWrapper layer_heigth_profile_wrapper(layer_height_profile); ar(name, module_name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper, sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation, - m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid); + m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid, + cut_connectors, cut_id); } template void load(Archive& ar) { ar(cereal::base_class(this)); @@ -541,7 +652,8 @@ private: SaveObjectGaurd gaurd(*this); ar(name, module_name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper, sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation, - m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid); + m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid, + cut_connectors, cut_id); std::vector volume_ids2; std::transform(volumes.begin(), volumes.end(), std::back_inserter(volume_ids2), std::mem_fn(&ObjectBase::id)); if (volume_ids != volume_ids2) @@ -708,6 +820,31 @@ public: }; Source source; + // struct used by cut command + // It contains information about connetors + struct CutInfo + { + bool is_connector{false}; + bool is_processed{true}; + CutConnectorType connector_type{CutConnectorType::Plug}; + float radius_tolerance{0.f}; // [0.f : 1.f] + float height_tolerance{0.f}; // [0.f : 1.f] + + CutInfo() = default; + CutInfo(CutConnectorType type, float rad_tolerance, float h_tolerance, bool processed = false) + : is_connector(true), is_processed(processed), connector_type(type), radius_tolerance(rad_tolerance), height_tolerance(h_tolerance) + {} + + void set_processed() { is_processed = true; } + void invalidate() { is_connector = false; } + + template inline void serialize(Archive &ar) { ar(is_connector, is_processed, connector_type, radius_tolerance, height_tolerance); } + }; + CutInfo cut_info; + + bool is_cut_connector() const { return cut_info.is_processed && cut_info.is_connector; } + void invalidate_cut_info() { cut_info.invalidate(); } + // The triangular model. const TriangleMesh& mesh() const { return *m_mesh.get(); } const TriangleMesh* mesh_ptr() const { return m_mesh.get(); } @@ -758,6 +895,8 @@ public: bool is_splittable() const; + void apply_tolerance(); + // BBS std::vector get_extruders() const; void update_extruder_count(size_t extruder_count); @@ -999,7 +1138,7 @@ private: // BBS: add backup, check modify bool mesh_changed = false; auto tr = m_transformation; - ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, m_text_info); + ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, m_text_info, cut_info); mesh_changed |= !(tr == m_transformation); if (mesh_changed) m_transformation.get_matrix(true, true, true, true); // force dirty auto t = supported_facets.timestamp(); @@ -1025,7 +1164,7 @@ private: } template void save(Archive &ar) const { bool has_convex_hull = m_convex_hull.get() != nullptr; - ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, m_text_info); + ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, m_text_info, cut_info); cereal::save_by_value(ar, supported_facets); cereal::save_by_value(ar, seam_facets); cereal::save_by_value(ar, mmu_segmentation_facets); diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index 1030171e7..b4e0507f4 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -65,6 +65,8 @@ protected: // Constructor with ignored int parameter to assign an invalid ID, to be replaced // by an existing ID copied from elsewhere. ObjectBase(int) : m_id(ObjectID(0)) {} + + ObjectBase(const ObjectID id) : m_id(id) {} // The class tree will have virtual tables and type information. virtual ~ObjectBase() = default; @@ -89,7 +91,6 @@ private: friend class cereal::access; friend class Slic3r::UndoRedo::StackImpl; template void serialize(Archive &ar) { ar(m_id); } - ObjectBase(const ObjectID id) : m_id(id) {} template static void load_and_construct(Archive & ar, cereal::construct &construct) { ObjectID id; ar(id); construct(id); } }; @@ -128,6 +129,67 @@ private: template void serialize(Archive &ar) { ar(m_timestamp); } }; +class CutObjectBase : public ObjectBase +{ + // check sum of CutParts in initial Object + size_t m_check_sum{1}; + // connectors count + size_t m_connectors_cnt{0}; + +public: + // Default Constructor to assign an invalid ID + CutObjectBase() : ObjectBase(-1) {} + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + CutObjectBase(int) : ObjectBase(-1) {} + // Constructor to initialize full information from 3mf + CutObjectBase(ObjectID id, size_t check_sum, size_t connectors_cnt) : ObjectBase(id), m_check_sum(check_sum), m_connectors_cnt(connectors_cnt) {} + // The class tree will have virtual tables and type information. + virtual ~CutObjectBase() = default; + + bool operator<(const CutObjectBase &other) const { return other.id() > this->id(); } + bool operator==(const CutObjectBase &other) const { return other.id() == this->id(); } + + void copy(const CutObjectBase &rhs) + { + this->copy_id(rhs); + this->m_check_sum = rhs.check_sum(); + this->m_connectors_cnt = rhs.connectors_cnt(); + } + CutObjectBase &operator=(const CutObjectBase &other) + { + this->copy(other); + return *this; + } + + void invalidate() + { + set_invalid_id(); + m_check_sum = 1; + m_connectors_cnt = 0; + } + + void init() { this->set_new_unique_id(); } + bool has_same_id(const CutObjectBase &rhs) { return this->id() == rhs.id(); } + bool is_equal(const CutObjectBase &rhs) { return this->id() == rhs.id() && this->check_sum() == rhs.check_sum() && this->connectors_cnt() == rhs.connectors_cnt(); } + + size_t check_sum() const { return m_check_sum; } + void set_check_sum(size_t cs) { m_check_sum = cs; } + void increase_check_sum(size_t cnt) { m_check_sum += cnt; } + + size_t connectors_cnt() const { return m_connectors_cnt; } + void increase_connectors_cnt(size_t connectors_cnt) { m_connectors_cnt += connectors_cnt; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) + { + ar(cereal::base_class(this)); + ar(m_check_sum, m_connectors_cnt); + } +}; + + // Unique object / instance ID for the wipe tower. extern ObjectID wipe_tower_object_id(); extern ObjectID wipe_tower_instance_id(); diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index c72bedafb..b29115762 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -566,6 +566,9 @@ namespace cereal { template void load(Archive& archive, Slic3r::Matrix2f &m) { archive.loadBinary((char*)m.data(), sizeof(float) * 4); } template void save(Archive& archive, Slic3r::Matrix2f &m) { archive.saveBinary((char*)m.data(), sizeof(float) * 4); } + + template void load(Archive &archive, Slic3r::Transform3d &m) { archive.loadBinary((char *) m.data(), sizeof(double) * 16); } + template void save(Archive &archive, const Slic3r::Transform3d &m) { archive.saveBinary((char *) m.data(), sizeof(double) * 16); } } // To be able to use Vec<> and Mat<> in range based for loops: diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 5fa361119..b5d0d1088 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -984,6 +984,61 @@ indexed_triangle_set its_make_cone(double r, double h, double fa) return mesh; } +// Generates mesh for a frustum dowel centered about the origin, using the count of sectors +// Note: This function uses code for sphere generation, but for stackCount = 2; +indexed_triangle_set its_make_frustum_dowel(double radius, double h, int sectorCount) +{ + int stackCount = 2; + float sectorStep = float(2. * M_PI / sectorCount); + float stackStep = float(M_PI / stackCount); + + indexed_triangle_set mesh; + auto& vertices = mesh.vertices; + vertices.reserve((stackCount - 1) * sectorCount + 2); + for (int i = 0; i <= stackCount; ++i) { + // from pi/2 to -pi/2 + double stackAngle = 0.5 * M_PI - stackStep * i; + double xy = radius * cos(stackAngle); + double z = radius * sin(stackAngle); + if (i == 0 || i == stackCount) + vertices.emplace_back(Vec3f(float(xy), 0.f, float(h * sin(stackAngle)))); + else + for (int j = 0; j < sectorCount; ++j) { + // from 0 to 2pi + double sectorAngle = sectorStep * j + 0.25 * M_PI; + vertices.emplace_back(Vec3d(xy * std::cos(sectorAngle), xy * std::sin(sectorAngle), z).cast()); + } + } + + auto& facets = mesh.indices; + facets.reserve(2 * (stackCount - 1) * sectorCount); + for (int i = 0; i < stackCount; ++i) { + // Beginning of current stack. + int k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount); + int k1_first = k1; + // Beginning of next stack. + int k2 = (i == 0) ? 1 : (k1 + sectorCount); + int k2_first = k2; + for (int j = 0; j < sectorCount; ++j) { + // 2 triangles per sector excluding first and last stacks + int k1_next = k1; + int k2_next = k2; + if (i != 0) { + k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1); + facets.emplace_back(k1, k2, k1_next); + } + if (i + 1 != stackCount) { + k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1); + facets.emplace_back(k1_next, k2, k2_next); + } + k1 = k1_next; + k2 = k2_next; + } + } + + return mesh; +} + indexed_triangle_set its_make_pyramid(float base, float height) { float a = base / 2.f; diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 79aac12cc..9afaddd4e 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -337,6 +337,7 @@ indexed_triangle_set its_make_cube(double x, double y, double z); indexed_triangle_set its_make_prism(float width, float length, float height); indexed_triangle_set its_make_cylinder(double r, double h, double fa=(2*PI/360)); indexed_triangle_set its_make_cone(double r, double h, double fa=(2*PI/360)); +indexed_triangle_set its_make_frustum_dowel(double r, double h, int sectorCount); indexed_triangle_set its_make_pyramid(float base, float height); indexed_triangle_set its_make_sphere(double radius, double fa); diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 3d60c924d..f33eb3df1 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -265,6 +265,8 @@ set(SLIC3R_GUI_SOURCES GUI/3DBed.hpp GUI/Camera.cpp GUI/Camera.hpp + GUI/CameraUtils.cpp + GUI/CameraUtils.hpp GUI/wxExtensions.cpp GUI/wxExtensions.hpp GUI/WipeTowerDialog.cpp diff --git a/src/slic3r/GUI/CameraUtils.cpp b/src/slic3r/GUI/CameraUtils.cpp new file mode 100644 index 000000000..99d022e4b --- /dev/null +++ b/src/slic3r/GUI/CameraUtils.cpp @@ -0,0 +1,131 @@ +#include "CameraUtils.hpp" +#include // projecting points +#include + +#include "slic3r/GUI/3DScene.hpp" // GLVolume +#include "libslic3r/Geometry/ConvexHull.hpp" + +using namespace Slic3r; +using namespace GUI; + +Points CameraUtils::project(const Camera & camera, + const std::vector &points) +{ + Vec4i viewport(camera.get_viewport().data()); + + // Convert our std::vector to Eigen dynamic matrix. + Eigen::Matrix + pts(points.size(), 3); + for (size_t i = 0; i < points.size(); ++i) + pts.block<1, 3>(i, 0) = points[i]; + + // Get the projections. + Eigen::Matrix projections; + igl::project(pts, camera.get_view_matrix().matrix(), + camera.get_projection_matrix().matrix(), viewport, projections); + + Points result; + result.reserve(points.size()); + int window_height = viewport[3]; + + // convert to points --> loss precision + for (int i = 0; i < projections.rows(); ++i) { + double x = projections(i, 0); + double y = projections(i, 1); + // opposit direction o Y + result.emplace_back(x, window_height - y); + } + return result; +} + +Point CameraUtils::project(const Camera &camera, const Vec3d &point) +{ + // IMPROVE: do it faster when you need it (inspire in project multi point) + return project(camera, std::vector{point}).front(); +} + +Slic3r::Polygon CameraUtils::create_hull2d(const Camera & camera, + const GLVolume &volume) +{ + std::vector vertices; + const TriangleMesh *hull = volume.convex_hull(); + if (hull != nullptr) { + const indexed_triangle_set &its = hull->its; + vertices.reserve(its.vertices.size()); + // cast vector + for (const Vec3f &vertex : its.vertices) + vertices.emplace_back(vertex.cast()); + } else { + // Negative volume doesn't have convex hull so use bounding box + auto bb = volume.bounding_box(); + Vec3d &min = bb.min; + Vec3d &max = bb.max; + vertices = {min, + Vec3d(min.x(), min.y(), max.z()), + Vec3d(min.x(), max.y(), min.z()), + Vec3d(min.x(), max.y(), max.z()), + Vec3d(max.x(), min.y(), min.z()), + Vec3d(max.x(), min.y(), max.z()), + Vec3d(max.x(), max.y(), min.z()), + max}; + } + + const Transform3d &trafoMat = + volume.get_instance_transformation().get_matrix() * + volume.get_volume_transformation().get_matrix(); + for (Vec3d &vertex : vertices) + vertex = trafoMat * vertex.cast(); + + Points vertices_2d = project(camera, vertices); + return Geometry::convex_hull(vertices_2d); +} + +void CameraUtils::ray_from_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction) { + switch (camera.get_type()) { + case Camera::EType::Ortho: return ray_from_ortho_screen_pos(camera, position, point, direction); + case Camera::EType::Perspective: return ray_from_persp_screen_pos(camera, position, point, direction); + default: break; + } +} + +Vec3d CameraUtils::screen_point(const Camera &camera, const Vec2d &position) +{ + double height = camera.get_viewport().data()[3]; + // Y coordinate has opposit direction + return Vec3d(position.x(), height - position.y(), 0.); +} + +void CameraUtils::ray_from_ortho_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction) +{ + assert(camera.get_type() == Camera::EType::Ortho); + Matrix4d modelview = camera.get_view_matrix().matrix(); + Matrix4d projection = camera.get_projection_matrix().matrix(); + Vec4i viewport(camera.get_viewport().data()); + igl::unproject(screen_point(camera,position), modelview, projection, viewport, point); + direction = camera.get_dir_forward(); +} +void CameraUtils::ray_from_persp_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction) +{ + assert(camera.get_type() == Camera::EType::Perspective); + Matrix4d modelview = camera.get_view_matrix().matrix(); + Matrix4d projection = camera.get_projection_matrix().matrix(); + Vec4i viewport(camera.get_viewport().data()); + igl::unproject(screen_point(camera, position), modelview, projection, viewport, point); + direction = point - camera.get_position(); +} + +Vec2d CameraUtils::get_z0_position(const Camera &camera, const Vec2d & coor) +{ + Vec3d p0, dir; + ray_from_screen_pos(camera, coor, p0, dir); + + // is approx zero + if ((fabs(dir.z()) - 1e-4) < 0) + return Vec2d(std::numeric_limits::max(), + std::numeric_limits::max()); + + // find position of ray cross plane(z = 0) + double t = p0.z() / dir.z(); + Vec3d p = p0 - t * dir; + return Vec2d(p.x(), p.y()); +} diff --git a/src/slic3r/GUI/CameraUtils.hpp b/src/slic3r/GUI/CameraUtils.hpp new file mode 100644 index 000000000..c3e938ec4 --- /dev/null +++ b/src/slic3r/GUI/CameraUtils.hpp @@ -0,0 +1,69 @@ +#ifndef slic3r_CameraUtils_hpp_ +#define slic3r_CameraUtils_hpp_ + +#include "Camera.hpp" +#include "libslic3r/Point.hpp" +namespace Slic3r { +class GLVolume; +} + +namespace Slic3r::GUI { +/// +/// Help divide camera data and camera functions +/// This utility work with camera data by static funtions +/// +class CameraUtils +{ +public: + CameraUtils() = delete; // only static functions + + /// + /// Project point throw camera to 2d coordinate into imgui window + /// + /// Projection params + /// Point to project. + /// projected points by camera into coordinate of camera. + /// x(from left to right), y(from top to bottom) + static Points project(const Camera& camera, const std::vector &points); + static Point project(const Camera& camera, const Vec3d &point); + + /// + /// Create hull around GLVolume in 2d space of camera + /// + /// Projection params + /// Outline by 3d object + /// Polygon around object + static Polygon create_hull2d(const Camera &camera, const GLVolume &volume); + + /// + /// Create ray(point and direction) for screen coordinate + /// + /// Definition of camera + /// Position on screen(aka mouse position) + /// OUT start of ray + /// OUT direction of ray + static void ray_from_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction); + static void ray_from_ortho_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction); + static void ray_from_persp_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction); + + /// + /// Unproject mouse coordinate to get position in space where z coor is zero + /// Platter surface should be in z == 0 + /// + /// Projection params + /// Mouse position + /// Position on platter under mouse + static Vec2d get_z0_position(const Camera &camera, const Vec2d &coor); + + /// + /// Create 3d screen point from 2d position + /// + /// Define camera viewport + /// Position on screen(aka mouse position) + /// Point represented screen coor in 3d + static Vec3d screen_point(const Camera &camera, const Vec2d &position); + +}; +} // Slic3r::GUI + +#endif /* slic3r_CameraUtils_hpp_ */ diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 876f16a30..6d5d5babd 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3861,6 +3861,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) default: { break; } } } + else if (evt.LeftUp() && + m_gizmos.get_current_type() == GLGizmosManager::EType::Scale && + m_gizmos.get_current()->get_state() == GLGizmoBase::EState::On) { + wxGetApp().obj_list()->selection_changed(); + } return; } @@ -6632,9 +6637,14 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with else m_volumes.set_z_range(-FLT_MAX, FLT_MAX); + GLGizmosManager& gm = get_gizmos_manager(); + GLGizmoBase* current_gizmo = gm.get_current(); if (m_canvas_type == CanvasAssembleView) { m_volumes.set_clipping_plane(m_gizmos.get_assemble_view_clipping_plane().get_data()); } + else if (current_gizmo && !current_gizmo->apply_clipping_plane()) { + m_volumes.set_clipping_plane(ClippingPlane::ClipsNothing().get_data()); + } else { m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); } diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index 33e5a0954..5f8ee9bc9 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -44,6 +44,10 @@ std::pair GLShadersManager::init() valid &= append_shader("gouraud_light_instanced", { "gouraud_light_instanced.vs", "gouraud_light_instanced.fs" }); // used to render extrusion and travel paths as lines in gcode preview valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" }); + + // used to render cut connectors + valid &= append_shader("gouraud_light_uniform", {"gouraud_light_uniform.vs", "gouraud_light_uniform.fs"}); + // used to render objects in 3d editor //if (GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 0)) { if (0) { diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 320493ca9..288fd9ada 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -914,6 +914,20 @@ void MenuFactory::append_menu_items_mirror(wxMenu* menu) []() { return plater()->can_mirror(); }, m_parent); } +void MenuFactory::append_menu_item_invalidate_cut_info(wxMenu *menu) +{ + const wxString menu_name = _L("Invalidate cut info"); + + auto menu_item_id = menu->FindItem(menu_name); + if (menu_item_id != wxNOT_FOUND) + // Delete old menu item if selected object isn't cut + menu->Destroy(menu_item_id); + + if (obj_list()->has_selected_cut_object()) + append_menu_item(menu, wxID_ANY, menu_name, "", [](wxCommandEvent &) { obj_list()->invalidate_cut_info_for_selection(); }, + "", menu, []() { return true; }, m_parent); +} + MenuFactory::MenuFactory() { for (int i = 0; i < mtCount; i++) { @@ -1235,6 +1249,7 @@ wxMenu* MenuFactory::object_menu() append_menu_item_change_filament(&m_object_menu); append_menu_items_convert_unit(&m_object_menu); append_menu_items_flush_options(&m_object_menu); + append_menu_item_invalidate_cut_info(&m_object_menu); return &m_object_menu; } diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index a3c01f961..8e2814b57 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -136,6 +136,8 @@ private: void append_menu_item_merge_to_single_object(wxMenu* menu); void append_menu_item_merge_parts_to_single_part(wxMenu *menu); void append_menu_items_mirror(wxMenu *menu); + void append_menu_item_invalidate_cut_info(wxMenu *menu); + //void append_menu_items_instance_manipulation(wxMenu *menu); //void update_menu_items_instance_manipulation(MenuType type); //BBS add bbl menu item diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 1edb3914e..3bd4ea2df 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -36,6 +36,7 @@ #include "wx/uiaction.h" #include #endif /* __WXMSW__ */ +#include "Gizmos/GLGizmoScale.hpp" namespace Slic3r { @@ -927,6 +928,8 @@ void ObjectList::selection_changed() fix_multiselection_conflicts(); + fix_cut_selection(); + // update object selection on Plater if (!m_prevent_canvas_selection_update) update_selections_on_canvas(); @@ -2264,9 +2267,9 @@ int ObjectList::load_mesh_part(const TriangleMesh &mesh, const wxString &name, c } //BBS -void ObjectList::del_object(const int obj_idx, bool refresh_immediately) +bool ObjectList::del_object(const int obj_idx, bool refresh_immediately) { - wxGetApp().plater()->delete_object_from_model(obj_idx, refresh_immediately); + return wxGetApp().plater()->delete_object_from_model(obj_idx, refresh_immediately); } // Delete subobject @@ -2283,6 +2286,7 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) wxDataViewItem parent = m_objects_model->GetParent(item); + InfoItemType item_info_type = m_objects_model->GetInfoItemType(item); if (type & itSettings) del_settings_from_config(parent); else if (type & itInstanceRoot && obj_idx != -1) @@ -2292,7 +2296,7 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) else if (type & itLayer && obj_idx != -1) del_layer_from_object(obj_idx, m_objects_model->GetLayerRangeByItem(item)); else if (type & itInfo && obj_idx != -1) - del_info_item(obj_idx, m_objects_model->GetInfoItemType(item)); + del_info_item(obj_idx, item_info_type); else if (idx == -1) return; else if (!del_subobject_from_object(obj_idx, idx, type)) @@ -2304,8 +2308,11 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) m_objects_model->UpdateWarningIcon(parent, icon_name); } - m_objects_model->Delete(item); - update_info_items(obj_idx); + if (!(type & itInfo) || item_info_type != InfoItemType::CutConnectors) { + // Connectors Item is already updated/deleted inside the del_info_item() + m_objects_model->Delete(item); + update_info_items(obj_idx); + } } void ObjectList::del_info_item(const int obj_idx, InfoItemType type) @@ -2329,6 +2336,13 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type) mv->mmu_segmentation_facets.reset(); break; + case InfoItemType::CutConnectors: + if (!del_from_cut_object(true)) { + // there is no need to post EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS if nothing was changed + return; + } + break; + // BBS: remove Sinking case InfoItemType::Undef : assert(false); break; } @@ -2399,6 +2413,38 @@ void ObjectList::del_layers_from_object(const int obj_idx) changed_object(obj_idx); } +bool ObjectList::del_from_cut_object(bool is_cut_connector, bool is_model_part/* = false*/, bool is_negative_volume/* = false*/) +{ + const long buttons_style = is_cut_connector ? (wxYES | wxNO | wxCANCEL) : (wxYES | wxCANCEL); + + const wxString title = is_cut_connector ? _L("Delete connector from object which is a part of cut") : + is_model_part ? _L("Delete solid part from object which is a part of cut") : + is_negative_volume ? _L("Delete negative volume from object which is a part of cut") : ""; + + const wxString msg_end = is_cut_connector ? ("\n" + _L("To save cut correspondence you can delete all connectors from all related objects.")) : ""; + + InfoDialog dialog(wxGetApp().plater(), title, + _L("This action will break a cut correspondence.\n" + "After that model consistency can't be guaranteed .\n" + "\n" + "To manipulate with solid parts or negative volumes you have to invalidate cut infornation first." + msg_end ), + false, buttons_style | wxCANCEL_DEFAULT | wxICON_WARNING); + + dialog.SetButtonLabel(wxID_YES, _L("Invalidate cut info")); + if (is_cut_connector) + dialog.SetButtonLabel(wxID_NO, _L("Delete all connectors")); + + const int answer = dialog.ShowModal(); + if (answer == wxID_CANCEL) + return false; + + if (answer == wxID_YES) + invalidate_cut_info_for_selection(); + else if (answer == wxID_NO) + delete_all_connectors_for_selection(); + return true; +} + bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, const int type) { assert(idx >= 0); @@ -2423,6 +2469,11 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con Slic3r::GUI::show_error(nullptr, _L("Deleting the last solid part is not allowed.")); return false; } + if (object->is_cut() && (volume->is_model_part() || volume->is_negative_volume())) { + del_from_cut_object(volume->is_cut_connector(), volume->is_model_part(), volume->is_negative_volume()); + // in any case return false to break the deletion + return false; + } take_snapshot("Delete part"); @@ -2927,6 +2978,9 @@ bool ObjectList::can_split_instances() bool ObjectList::can_merge_to_multipart_object() const { + if (has_selected_cut_object()) + return false; + if (printer_technology() == ptSLA) return false; @@ -2954,6 +3008,97 @@ bool ObjectList::can_merge_to_single_object() const return (*m_objects)[obj_idx]->volumes.size() > 1; } +bool ObjectList::has_selected_cut_object() const +{ + wxDataViewItemArray sels; + GetSelections(sels); + if (sels.IsEmpty()) + return false; + + for (wxDataViewItem item : sels) { + const int obj_idx = m_objects_model->GetObjectIdByItem(item); + if (obj_idx >= 0 && object(obj_idx)->is_cut()) + return true; + } + + return false; +} + +void ObjectList::invalidate_cut_info_for_selection() +{ + const wxDataViewItem item = GetSelection(); + if (item) { + const int obj_idx = m_objects_model->GetObjectIdByItem(item); + if (obj_idx >= 0) + invalidate_cut_info_for_object(size_t(obj_idx)); + } +} + +void ObjectList::invalidate_cut_info_for_object(int obj_idx) +{ + ModelObject *init_obj = object(obj_idx); + if (!init_obj->is_cut()) return; + + take_snapshot("Invalidate cut info"); + + const CutObjectBase cut_id = init_obj->cut_id; + // invalidate cut for related objects (which have the same cut_id) + for (size_t idx = 0; idx < m_objects->size(); idx++) + if (ModelObject *obj = object(int(idx)); obj->cut_id.is_equal(cut_id)) { + obj->invalidate_cut(); + update_info_items(idx); + add_volumes_to_object_in_list(idx); + } + + update_lock_icons_for_model(); +} + +void ObjectList::delete_all_connectors_for_selection() +{ + const wxDataViewItem item = GetSelection(); + if (item) { + const int obj_idx = m_objects_model->GetObjectIdByItem(item); + if (obj_idx >= 0) + delete_all_connectors_for_object(size_t(obj_idx)); + } +} + +void ObjectList::delete_all_connectors_for_object(int obj_idx) +{ + ModelObject *init_obj = object(obj_idx); + if (!init_obj->is_cut()) + return; + + take_snapshot("Delete all connectors"); + + auto has_solid_mesh = [](ModelObject* obj) { + for (const ModelVolume *volume : obj->volumes) + if (volume->is_model_part()) return true; + return false; + }; + + const CutObjectBase cut_id = init_obj->cut_id; + // Delete all connectors for related objects (which have the same cut_id) + Model &model = wxGetApp().plater()->model(); + for (int idx = int(m_objects->size()) - 1; idx >= 0; idx--) + if (ModelObject *obj = object(idx); obj->cut_id.is_equal(cut_id)) { + obj->delete_connectors(); + + if (obj->volumes.empty() || !has_solid_mesh(obj)) { + model.delete_object(idx); + m_objects_model->Delete(m_objects_model->GetItemById(idx)); + continue; + } + + update_info_items(idx); + add_volumes_to_object_in_list(idx); + changed_object(int(idx)); + } + + update_lock_icons_for_model(); +} + + // NO_PARAMETERS function call means that changed object index will be determine from Selection() void ObjectList::changed_object(const int obj_idx/* = -1*/) const { @@ -2972,15 +3117,77 @@ void ObjectList::part_selection_changed() bool update_and_show_settings = false; bool update_and_show_layers = false; + bool enable_manipulation{true}; + bool disable_ss_manipulation{false}; + bool disable_ununiform_scale{false}; + const auto item = GetSelection(); - // BBS - if (item && (m_objects_model->GetItemType(item) & itPlate)) { + if (item && m_objects_model->GetItemType(item) == itInfo && m_objects_model->GetInfoItemType(item) == InfoItemType::CutConnectors) { + og_name = _L("Cut Connectors information"); + + update_and_show_manipulations = true; + enable_manipulation = false; + disable_ununiform_scale = true; + } + else if (item && (m_objects_model->GetItemType(item) & itPlate)) { + // BBS // TODO: og for plate } else if ( multiple_selection() || (item && m_objects_model->GetItemType(item) == itInstanceRoot )) { const Selection& selection = scene_selection(); - // don't show manipulation panel for case of all Object's parts selection - update_and_show_manipulations = !selection.is_single_full_instance(); + + if (selection.is_single_full_object()) { + og_name = _L("Object manipulation"); + update_and_show_manipulations = true; + + obj_idx = selection.get_object_idx(); + ModelObject *object = (*m_objects)[obj_idx]; + m_config = &object->config; + disable_ss_manipulation = object->is_cut(); + } + else { + og_name = _L("Group manipulation"); + + // don't show manipulation panel for case of all Object's parts selection + update_and_show_manipulations = !selection.is_single_full_instance(); + + if (int obj_idx = selection.get_object_idx(); obj_idx >= 0) { + if (selection.is_any_volume() || selection.is_any_modifier()) + enable_manipulation = !(*m_objects)[obj_idx]->is_cut(); + else // if (item && m_objects_model->GetItemType(item) == itInstanceRoot) + disable_ss_manipulation = (*m_objects)[obj_idx]->is_cut(); + } + else { + wxDataViewItemArray sels; + GetSelections(sels); + if (selection.is_single_full_object() || selection.is_multiple_full_instance()) { + int obj_idx = m_objects_model->GetObjectIdByItem(sels.front()); + disable_ss_manipulation = (*m_objects)[obj_idx]->is_cut(); + } else if (selection.is_mixed() || selection.is_multiple_full_object()) { + std::map> cut_objects; + + // find cut objects + for (auto item : sels) { + int obj_idx = m_objects_model->GetObjectIdByItem(item); + const ModelObject *obj = object(obj_idx); + if (obj->is_cut()) { + if (cut_objects.find(obj->cut_id) == cut_objects.end()) + cut_objects[obj->cut_id] = std::set{obj_idx}; + else + cut_objects.at(obj->cut_id).insert(obj_idx); + } + } + + // check if selected cut objects are "full selected" + for (auto cut_object : cut_objects) + if (cut_object.first.check_sum() != cut_object.second.size()) { + disable_ss_manipulation = true; + break; + } + disable_ununiform_scale = !cut_objects.empty(); + } + } + } // BBS: multi config editing update_and_show_settings = true; @@ -3022,34 +3229,44 @@ void ObjectList::part_selection_changed() // BBS: select object to edit config m_config = &(*m_objects)[obj_idx]->config; update_and_show_settings = true; + disable_ss_manipulation = (*m_objects)[obj_idx]->is_cut(); } } else { if (type & itSettings) { if (parent_type & itObject) { + og_name = _L("Object Settings to modify"); m_config = &(*m_objects)[obj_idx]->config; } else if (parent_type & itVolume) { + og_name = _L("Part Settings to modify"); volume_id = m_objects_model->GetVolumeIdByItem(parent); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; } else if (parent_type & itLayer) { + og_name = _L("Layer range Settings to modify"); m_config = &get_item_config(parent); } update_and_show_settings = true; } else if (type & itVolume) { + og_name = _L("Part manipulation"); volume_id = m_objects_model->GetVolumeIdByItem(item); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; update_and_show_manipulations = true; m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; update_and_show_settings = true; + + const ModelVolume *volume = (*m_objects)[obj_idx]->volumes[volume_id]; + enable_manipulation = !((*m_objects)[obj_idx]->is_cut() && (volume->is_cut_connector() || volume->is_model_part())); } else if (type & itInstance) { + og_name = _L("Instance manipulation"); update_and_show_manipulations = true; // fill m_config by object's values m_config = &(*m_objects)[obj_idx]->config; + disable_ss_manipulation = (*m_objects)[obj_idx]->is_cut(); } // BBS: remove height range logics } @@ -3068,6 +3285,11 @@ void ObjectList::part_selection_changed() //wxGetApp().obj_manipul()->update_item_name(m_objects_model->GetName(item)); //wxGetApp().obj_manipul()->update_warning_icon_state(get_mesh_errors_info(obj_idx, volume_id)); } + + GLGizmosManager &gizmos_mgr = wxGetApp().plater()->canvas3D()->get_gizmos_manager(); + + if (GLGizmoScale3D *scale = dynamic_cast(gizmos_mgr.get_gizmo(GLGizmosManager::Scale))) + scale->enable_ununiversal_scale(!disable_ununiform_scale); } #if !NEW_OBJECT_SETTING @@ -3162,6 +3384,33 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio wxDataViewItem item_obj = m_objects_model->GetItemById(obj_idx); assert(item_obj.IsOk()); + // Cut connectors + { + wxDataViewItem item = m_objects_model->GetInfoItemByType(item_obj, InfoItemType::CutConnectors); + bool shows = item.IsOk(); + bool should_show = model_object->is_cut() && model_object->has_connectors() && model_object->volumes.size() > 1; + + if (!shows && should_show) { + m_objects_model->AddInfoChild(item_obj, InfoItemType::CutConnectors); + Expand(item_obj); + if (added_object) + wxGetApp().notification_manager()->push_updated_item_info_notification(InfoItemType::CutConnectors); + } else if (shows && !should_show) { + if (!selections) Unselect(item); + m_objects_model->Delete(item); + if (selections) { + if (selections->Index(item) != wxNOT_FOUND) { + // If info item was deleted from the list, + // it's need to be deleted from selection array, if it was there + selections->Remove(item); + // Select item_obj, if info_item doesn't exist for item anymore, but was selected + if (selections->Index(item_obj) == wxNOT_FOUND) selections->Add(item_obj); + } + } else + Select(item_obj); + } + } + { bool shows = m_objects_model->IsSupportPainted(item_obj); bool should_show = printer_technology() == ptFFF @@ -3285,24 +3534,12 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed, //const wxString& item_name = from_u8(item_name_str); const wxString& item_name = from_u8(model_object->name); std::string warning_bitmap = get_warning_icon_name(model_object->mesh().stats()); - const auto item = m_objects_model->AddObject(model_object, warning_bitmap); + const auto item = m_objects_model->AddObject(model_object, warning_bitmap, model_object->is_cut()); Expand(m_objects_model->GetParent(item)); update_info_items(obj_idx, nullptr, call_selection_changed); - // add volumes to the object - if (model_object->volumes.size() > 1) { - for (const ModelVolume* volume : model_object->volumes) { - const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(item, - from_u8(volume->name), - volume->type(), - get_warning_icon_name(volume->mesh().stats()), - volume->config.has("extruder") ? volume->config.extruder() : 0, - false); - add_settings_item(vol_item, &volume->config.get()); - } - Expand(item); - } + add_volumes_to_object_in_list(obj_idx); // add instances to the object, if it has those if (model_object->instances.size()>1) @@ -3336,6 +3573,68 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed, #endif //__WXMSW__ } +static bool can_add_volumes_to_object(const ModelObject *object) +{ + bool can = object->volumes.size() > 1; + + if (can && object->is_cut()) { + int no_connectors_cnt = 0; + for (const ModelVolume *v : object->volumes) + if (!v->is_cut_connector()) { + if (!v->is_model_part()) + return true; + no_connectors_cnt++; + } + can = no_connectors_cnt > 1; + } + + return can; +} + +wxDataViewItemArray ObjectList::add_volumes_to_object_in_list(size_t obj_idx, std::function add_to_selection /* = nullptr*/) +{ + const bool is_prevent_list_events = m_prevent_list_events; + m_prevent_list_events = true; + + wxDataViewItem object_item = m_objects_model->GetItemById(int(obj_idx)); + m_objects_model->DeleteVolumeChildren(object_item); + + wxDataViewItemArray items; + + const ModelObject *object = (*m_objects)[obj_idx]; + // add volumes to the object + if (can_add_volumes_to_object(object)) { + if (object->volumes.size() > 1) { + wxString obj_item_name = from_u8(object->name); + if (m_objects_model->GetName(object_item) != obj_item_name) + m_objects_model->SetName(obj_item_name, object_item); + } + + int volume_idx{-1}; + for (const ModelVolume *volume : object->volumes) { + ++volume_idx; + if (object->is_cut() && volume->is_cut_connector()) + continue; + + const wxDataViewItem &vol_item = m_objects_model->AddVolumeChild( + object_item, + from_u8(volume->name), + volume->type(), + get_warning_icon_name(volume->mesh().stats()), + volume->config.has("extruder") ? volume->config.extruder() : 0, + false); + add_settings_item(vol_item, &volume->config.get()); + + if (add_to_selection && add_to_selection(volume)) + items.Add(vol_item); + } + Expand(object_item); + } + + m_prevent_list_events = is_prevent_list_events; + return items; +} + void ObjectList::delete_object_from_list() { auto item = GetSelection(); @@ -3370,8 +3669,12 @@ void ObjectList::delete_from_model_and_list(const ItemType type, const int obj_i take_snapshot("Delete selected"); if (type&itObject) { - del_object(obj_idx); - delete_object_from_list(obj_idx); + bool was_cut = object(obj_idx)->is_cut(); + if (del_object(obj_idx)) { + delete_object_from_list(obj_idx); + if (was_cut) + update_lock_icons_for_model(); + } } else { del_subobject_from_object(obj_idx, sub_obj_idx, type); @@ -3398,12 +3701,16 @@ void ObjectList::delete_from_model_and_list(const std::vector& it // refresh after del_object need_update = true; bool refresh_immediately = false; - del_object(item->obj_idx, refresh_immediately); + bool was_cut = object(item->obj_idx)->is_cut(); + if (!del_object(item->obj_idx, refresh_immediately)) + return; m_objects_model->Delete(m_objects_model->GetItemById(item->obj_idx)); + if (was_cut) + update_lock_icons_for_model(); } else { if (!del_subobject_from_object(item->obj_idx, item->sub_obj_idx, item->type)) - continue; + return; //continue; if (item->type&itVolume) { m_objects_model->Delete(m_objects_model->GetItemByVolumeId(item->obj_idx, item->sub_obj_idx)); // BBS @@ -3441,6 +3748,14 @@ void ObjectList::delete_from_model_and_list(const std::vector& it part_selection_changed(); } +void ObjectList::update_lock_icons_for_model() +{ + // update the icon for cut object + for (size_t obj_idx = 0; obj_idx < (*m_objects).size(); ++obj_idx) + if (!(*m_objects)[obj_idx]->is_cut()) + m_objects_model->UpdateCutObjectIcon(m_objects_model->GetItemById(int(obj_idx)), false); +} + void ObjectList::delete_all_objects_from_list() { m_prevent_list_events = true; @@ -3856,6 +4171,25 @@ bool ObjectList::is_selected(const ItemType type) const return false; } +bool ObjectList::is_connectors_item_selected() const +{ + const wxDataViewItem &item = GetSelection(); + if (item) + return m_objects_model->GetItemType(item) == itInfo && m_objects_model->GetInfoItemType(item) == InfoItemType::CutConnectors; + + return false; +} + +bool ObjectList::is_connectors_item_selected(const wxDataViewItemArray &sels) const +{ + for (auto item : sels) + if (m_objects_model->GetItemType(item) == itInfo && m_objects_model->GetInfoItemType(item) == InfoItemType::CutConnectors) + return true; + + return false; +} + + int ObjectList::get_selected_layers_range_idx() const { const wxDataViewItem& item = GetSelection(); @@ -3991,11 +4325,18 @@ void ObjectList::update_selections() else { for (auto idx : selection.get_volume_idxs()) { const auto gl_vol = selection.get_volume(idx); - if (gl_vol->volume_idx() >= 0) + if (gl_vol->volume_idx() >= 0) { // Only add GLVolumes with non-negative volume_ids. GLVolumes with negative volume ids // are not associated with ModelVolumes, but they are temporarily generated by the backend // (for example, SLA supports or SLA pad). - sels.Add(m_objects_model->GetItemByVolumeId(gl_vol->object_idx(), gl_vol->volume_idx())); + int obj_idx = gl_vol->object_idx(); + int vol_idx = gl_vol->volume_idx(); + assert(obj_idx >= 0 && vol_idx >= 0); + if (object(obj_idx)->volumes[vol_idx]->is_cut_connector()) + sels.Add(m_objects_model->GetInfoItemByType(m_objects_model->GetItemById(obj_idx), InfoItemType::CutConnectors)); + else + sels.Add(m_objects_model->GetItemByVolumeId(obj_idx, vol_idx)); + } } m_selection_mode = smVolume; } } @@ -4046,10 +4387,33 @@ void ObjectList::update_selections() if (sels.size() == 0 || m_selection_mode & smSettings) m_selection_mode = smUndef; - select_items(sels); + if (fix_cut_selection(sels) || is_connectors_item_selected(sels)) { + m_prevent_list_events = true; - // Scroll selected Item in the middle of an object list - ensure_current_item_visible(); + // If some part is selected, unselect all items except of selected parts of the current object + UnselectAll(); + SetSelections(sels); + + m_prevent_list_events = false; + + // update object selection on Plater + if (!m_prevent_canvas_selection_update) + update_selections_on_canvas(); + + // to update the toolbar and info sizer + if (!GetSelection() || m_objects_model->GetItemType(GetSelection()) == itObject || is_connectors_item_selected()) { + auto event = SimpleEvent(EVT_OBJ_LIST_OBJECT_SELECT); + event.SetEventObject(this); + wxPostEvent(this, event); + } + part_selection_changed(); + } + else { + select_items(sels); + + // Scroll selected Item in the middle of an object list + ensure_current_item_visible(); + } } void ObjectList::update_selections_on_canvas() @@ -4083,16 +4447,27 @@ void ObjectList::update_selections_on_canvas() volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); } else if (type == itInfo) { - // When selecting an info item, select one instance of the - // respective object - a gizmo may want to be opened. - int inst_idx = selection.get_instance_idx(); - int scene_obj_idx = selection.get_object_idx(); - mode = Selection::Instance; - // select first instance, unless an instance of the object is already selected - if (scene_obj_idx == -1 || inst_idx == -1 || scene_obj_idx != obj_idx) - inst_idx = 0; - std::vector idxs = selection.get_volume_idxs_from_instance(obj_idx, inst_idx); - volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); + if (m_objects_model->GetInfoItemType(item) == InfoItemType::CutConnectors) { + mode = Selection::Volume; + + // When selecting CutConnectors info item, select all object volumes, which are marked as a connector + const ModelObject *obj = object(obj_idx); + for (unsigned int vol_idx = 0; vol_idx < obj->volumes.size(); vol_idx++) + if (obj->volumes[vol_idx]->is_cut_connector()) { + std::vector idxs = selection.get_volume_idxs_from_volume(obj_idx, std::max(instance_idx, 0), vol_idx); + volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); + } + } else { + // When selecting an info item, select one instance of the + // respective object - a gizmo may want to be opened. + int inst_idx = selection.get_instance_idx(); + int scene_obj_idx = selection.get_object_idx(); + mode = Selection::Instance; + // select first instance, unless an instance of the object is already selected + if (scene_obj_idx == -1 || inst_idx == -1 || scene_obj_idx != obj_idx) inst_idx = 0; + std::vector idxs = selection.get_volume_idxs_from_instance(obj_idx, inst_idx); + volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); + } } else { @@ -4108,6 +4483,9 @@ void ObjectList::update_selections_on_canvas() if (sel_cnt == 1) { wxDataViewItem item = GetSelection(); + if (m_objects_model->GetInfoItemType(item) == InfoItemType::CutConnectors) + selection.remove_all(); + if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer)) add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, mode); else @@ -4434,6 +4812,51 @@ void ObjectList::fix_multiselection_conflicts() m_prevent_list_events = false; } +void ObjectList::fix_cut_selection() +{ + wxDataViewItemArray sels; + GetSelections(sels); + if (fix_cut_selection(sels)) { + m_prevent_list_events = true; + + // If some part is selected, unselect all items except of selected parts of the current object + UnselectAll(); + SetSelections(sels); + + m_prevent_list_events = false; + } +} + +bool ObjectList::fix_cut_selection(wxDataViewItemArray &sels) +{ + if (wxGetApp().plater()->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Scale) { + for (const auto &item : sels) { + if (m_objects_model->GetItemType(item) & (itInstance | itObject) || + (m_objects_model->GetItemType(item) & itSettings && m_objects_model->GetItemType(m_objects_model->GetParent(item)) & itObject)) { + bool is_instance_selection = m_objects_model->GetItemType(item) & itInstance; + + int object_idx = m_objects_model->GetObjectIdByItem(item); + int inst_idx = is_instance_selection ? m_objects_model->GetInstanceIdByItem(item) : 0; + + if (auto obj = object(object_idx); obj->is_cut()) { + sels.Clear(); + + auto cut_id = obj->cut_id; + + int objects_cnt = int((*m_objects).size()); + for (int obj_idx = 0; obj_idx < objects_cnt; ++obj_idx) { + auto object = (*m_objects)[obj_idx]; + if (object->is_cut() && object->cut_id.has_same_id(cut_id)) + sels.Add(is_instance_selection ? m_objects_model->GetItemByInstanceId(obj_idx, inst_idx) : m_objects_model->GetItemById(obj_idx)); + } + return true; + } + } + } + } + return false; +} + ModelVolume* ObjectList::get_selected_model_volume() { wxDataViewItem item = GetSelection(); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 332ab4582..da1302ed5 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -286,12 +286,13 @@ public: // BBS void switch_to_object_process(); int load_mesh_part(const TriangleMesh &mesh, const wxString &name, const TextInfo &text_info, bool is_temp); - void del_object(const int obj_idx, bool refresh_immediately = true); + bool del_object(const int obj_idx, bool refresh_immediately = true); void del_subobject_item(wxDataViewItem& item); void del_settings_from_config(const wxDataViewItem& parent_item); void del_instances_from_object(const int obj_idx); void del_layer_from_object(const int obj_idx, const t_layer_height_range& layer_range); void del_layers_from_object(const int obj_idx); + bool del_from_cut_object(bool is_connector, bool is_model_part = false, bool is_negative_volume = false); bool del_subobject_from_object(const int obj_idx, const int idx, const int type); void del_info_item(const int obj_idx, InfoItemType type); void split(); @@ -310,6 +311,12 @@ public: bool can_merge_to_multipart_object() const; bool can_merge_to_single_object() const; + bool has_selected_cut_object() const; + void invalidate_cut_info_for_selection(); + void invalidate_cut_info_for_object(int obj_idx); + void delete_all_connectors_for_selection(); + void delete_all_connectors_for_object(int obj_idx); + wxPoint get_mouse_position_in_control() const { return wxGetMousePosition() - this->GetScreenPosition(); } int get_selected_obj_idx() const; ModelConfig& get_item_config(const wxDataViewItem& item) const; @@ -319,6 +326,9 @@ public: // Add object to the list void add_object_to_list(size_t obj_idx, bool call_selection_changed = true, bool notify_partplate = true); + // Add object's volumes to the list + // Return selected items, if add_to_selection is defined + wxDataViewItemArray add_volumes_to_object_in_list(size_t obj_idx, std::function add_to_selection = nullptr); // Delete object from the list void delete_object_from_list(); void delete_object_from_list(const size_t obj_idx); @@ -326,6 +336,7 @@ public: void delete_instance_from_list(const size_t obj_idx, const size_t inst_idx); void delete_from_model_and_list(const ItemType type, const int obj_idx, const int sub_obj_idx); void delete_from_model_and_list(const std::vector& items_for_delete); + void update_lock_icons_for_model(); // Delete all objects from the list void delete_all_objects_from_list(); // Increase instances count @@ -368,6 +379,8 @@ public: void init(); bool multiple_selection() const ; bool is_selected(const ItemType type) const; + bool is_connectors_item_selected() const; + bool is_connectors_item_selected(const wxDataViewItemArray &sels) const; int get_selected_layers_range_idx() const; void set_selected_layers_range_idx(const int range_idx) { m_selected_layers_range_idx = range_idx; } void set_selection_mode(SELECTION_MODE mode) { m_selection_mode = mode; } @@ -385,6 +398,9 @@ public: bool check_last_selection(wxString& msg_str); // correct current selections to avoid of the possible conflicts void fix_multiselection_conflicts(); + // correct selection in respect to the cut_id if any exists + void fix_cut_selection(); + bool fix_cut_selection(wxDataViewItemArray &sels); ModelVolume* get_selected_model_volume(); void change_part_type(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp index c8e9e8470..c9833c281 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp @@ -19,6 +19,56 @@ namespace Slic3r { namespace GUI { + +const double units_in_to_mm = 25.4; +const double units_mm_to_in = 1 / units_in_to_mm; + +const int c_connectors_group_id = 4; +const float UndefFloat = -999.f; + +// connector colors + +using ColorRGBA = std::array; + +static const ColorRGBA BLACK() { return {0.0f, 0.0f, 0.0f, 1.0f}; } +static const ColorRGBA BLUE() { return {0.0f, 0.0f, 1.0f, 1.0f}; } +static const ColorRGBA BLUEISH() { return {0.5f, 0.5f, 1.0f, 1.0f}; } +static const ColorRGBA CYAN() { return {0.0f, 1.0f, 1.0f, 1.0f}; } +static const ColorRGBA DARK_GRAY() { return {0.25f, 0.25f, 0.25f, 1.0f}; } +static const ColorRGBA DARK_YELLOW() { return {0.5f, 0.5f, 0.0f, 1.0f}; } +static const ColorRGBA GRAY() { return {0.5f, 0.5f, 0.5f, 1.0f}; } +static const ColorRGBA GREEN() { return {0.0f, 1.0f, 0.0f, 1.0f}; } +static const ColorRGBA GREENISH() { return {0.5f, 1.0f, 0.5f, 1.0f}; } +static const ColorRGBA LIGHT_GRAY() { return {0.75f, 0.75f, 0.75f, 1.0f}; } +static const ColorRGBA MAGENTA() { return {1.0f, 0.0f, 1.0f, 1.0f}; } +static const ColorRGBA ORANGE() { return {0.923f, 0.504f, 0.264f, 1.0f}; } +static const ColorRGBA RED() { return {1.0f, 0.0f, 0.0f, 1.0f}; } +static const ColorRGBA REDISH() { return {1.0f, 0.5f, 0.5f, 1.0f}; } +static const ColorRGBA YELLOW() { return {1.0f, 1.0f, 0.0f, 1.0f}; } +static const ColorRGBA WHITE() { return {1.0f, 1.0f, 1.0f, 1.0f}; } + +static const ColorRGBA PLAG_COLOR = YELLOW(); +static const ColorRGBA DOWEL_COLOR = DARK_YELLOW(); +static const ColorRGBA HOVERED_PLAG_COLOR = CYAN(); +static const ColorRGBA HOVERED_DOWEL_COLOR = {0.0f, 0.5f, 0.5f, 1.0f}; +static const ColorRGBA SELECTED_PLAG_COLOR = GRAY(); +static const ColorRGBA SELECTED_DOWEL_COLOR = GRAY(); // DARK_GRAY(); +static const ColorRGBA CONNECTOR_DEF_COLOR = {1.0f, 1.0f, 1.0f, 0.5f}; +static const ColorRGBA CONNECTOR_ERR_COLOR = {1.0f, 0.3f, 0.3f, 0.5f}; +static const ColorRGBA HOVERED_ERR_COLOR = {1.0f, 0.3f, 0.3f, 1.0f}; + +static Vec3d rotate_vec3d_around_vec3d_with_rotate_matrix( + const Vec3d& rotate_point, + const Vec3d& origin_point, + const Transform3d& rotate_matrix) +{ + Transform3d translate_to_point = Transform3d::Identity(); + translate_to_point.translate(origin_point); + Transform3d translate_to_zero = Transform3d::Identity(); + translate_to_zero.translate(-origin_point); + return (translate_to_point * rotate_matrix * translate_to_zero) * rotate_point; +} + static inline void rotate_point_2d(double& x, double& y, const double c, const double s) { double xold = x; @@ -63,11 +113,12 @@ GLGizmoAdvancedCut::GLGizmoAdvancedCut(GLCanvas3D& parent, const std::string& ic , m_last_active_id(0) , m_keep_upper(true) , m_keep_lower(true) - , m_rotate_lower(false) - , m_cut_to_parts(false) , m_do_segment(false) , m_segment_smoothing_alpha(0.5) , m_segment_number(5) + , m_connector_type(CutConnectorType::Plug) + , m_connector_style(size_t(CutConnectorStyle::Prizm)) + , m_connector_shape_id(size_t(CutConnectorShape::Circle)) { for (int i = 0; i < 4; i++) m_cut_plane_points[i] = { 0., 0., 0. }; @@ -79,11 +130,140 @@ GLGizmoAdvancedCut::GLGizmoAdvancedCut(GLCanvas3D& parent, const std::string& ic m_buffered_rotation.setZero(); } +bool GLGizmoAdvancedCut::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_position, bool shift_down, bool alt_down, bool control_down) +{ + CutConnectors &connectors = m_c->selection_info()->model_object()->cut_connectors; + + if (shift_down && !m_connectors_editing && + (action == SLAGizmoEventType::LeftDown || action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::Dragging)) { + process_cut_line(action, mouse_position); + return true; + } + + if (action == SLAGizmoEventType::LeftDown) { + if (!m_connectors_editing) + return false; + + if (m_hover_id != -1) { + start_dragging(); + return true; + } + + if (shift_down || alt_down) { + // left down with shift - show the selection rectangle: + //if (m_hover_id == -1) + // m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); + } else { + // If there is no selection and no hovering, add new point + if (m_hover_id == -1 && !shift_down && !alt_down) + add_connector(connectors, mouse_position); + //m_ldown_mouse_position = mouse_position; + } + return true; + } + else if (action == SLAGizmoEventType::LeftUp) { + if (m_hover_id == -1 && !shift_down && !alt_down) + unselect_all_connectors(); + + is_selection_changed(alt_down, shift_down); + return true; + } + else if (action == SLAGizmoEventType::RightDown) { + if (m_hover_id < c_connectors_group_id) + return false; + + unselect_all_connectors(); + select_connector(m_hover_id - c_connectors_group_id, true); + return delete_selected_connectors(); + } + else if (action == SLAGizmoEventType::RightUp) { + // catch right click event + return true; + } + + return false; +} + +bool GLGizmoAdvancedCut::on_key(wxKeyEvent &evt) +{ + bool ctrl_down = evt.GetModifiers() & wxMOD_CONTROL; + + if (evt.GetKeyCode() == WXK_DELETE) { + return delete_selected_connectors(); + } + else if (ctrl_down + && (evt.GetKeyCode() == 'A' || evt.GetKeyCode() == 'a')) + { + select_all_connectors(); + return true; + } + return false; +} + std::string GLGizmoAdvancedCut::get_tooltip() const { return ""; } +BoundingBoxf3 GLGizmoAdvancedCut::bounding_box() const +{ + BoundingBoxf3 ret; + const Selection & selection = m_parent.get_selection(); + const Selection::IndicesList &idxs = selection.get_volume_idxs(); + for (unsigned int i : idxs) { + const GLVolume *volume = selection.get_volume(i); + // respect just to the solid parts for FFF and ignore pad and supports for SLA + if (!volume->is_modifier && !volume->is_sla_pad() && !volume->is_sla_support()) ret.merge(volume->transformed_convex_hull_bounding_box()); + } + return ret; +} + +bool GLGizmoAdvancedCut::is_looking_forward() const +{ + const Camera &camera = wxGetApp().plater()->get_camera(); + const double dot = camera.get_dir_forward().dot(m_cut_plane_normal); + return dot < 0.05; +} + +// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal +// Return false if no intersection was found, true otherwise. +bool GLGizmoAdvancedCut::unproject_on_cut_plane(const Vec2d &mouse_pos, Vec3d &pos, Vec3d &pos_world) +{ + const float sla_shift = m_c->selection_info()->get_sla_shift(); + + const ModelObject * mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[m_c->selection_info()->get_active_instance()]; + const Camera & camera = wxGetApp().plater()->get_camera(); + + // Calculate intersection with the clipping plane. + const ClippingPlane *cp = m_c->object_clipper()->get_clipping_plane(); + Vec3d point; + Vec3d direction; + Vec3d hit; + MeshRaycaster::line_from_mouse_pos_static(mouse_pos, Transform3d::Identity(), camera, point, direction); + Vec3d normal = -cp->get_normal().cast(); + double den = normal.dot(direction); + if (den != 0.) { + double t = (-cp->get_offset() - normal.dot(point)) / den; + hit = (point + t * direction); + } else + return false; + + if (!m_c->object_clipper()->is_projection_inside_cut(hit)) + return false; + + // recalculate hit to object's local position + Vec3d hit_d = hit; + hit_d -= mi->get_offset(); + hit_d[Z] -= sla_shift; + + // Return both the point and the facet normal. + pos = hit_d; + pos_world = hit; + + return true; +} + void GLGizmoAdvancedCut::update_plane_points() { Vec3d plane_center = get_plane_center(); @@ -170,7 +350,10 @@ void GLGizmoAdvancedCut::reset_all() m_keep_upper = true; m_keep_lower = true; - m_cut_to_parts = false; + m_place_on_cut_upper = true; + m_place_on_cut_lower = false; + m_rotate_upper = false; + m_rotate_lower = false; } bool GLGizmoAdvancedCut::on_init() @@ -179,6 +362,20 @@ bool GLGizmoAdvancedCut::on_init() return false; m_shortcut_key = WXK_CONTROL_C; + + // initiate info shortcuts + const wxString ctrl = GUI::shortkey_ctrl_prefix(); + const wxString alt = GUI::shortkey_alt_prefix(); + const wxString shift = "Shift+"; + + m_shortcuts.push_back(std::make_pair(_L("Left click"), _L("Add connector"))); + m_shortcuts.push_back(std::make_pair(_L("Right click"), _L("Remove connector"))); + m_shortcuts.push_back(std::make_pair(_L("Drag"), _L("Move connector"))); + m_shortcuts.push_back(std::make_pair(shift + _L("Left click"), _L("Add connector to selection"))); + m_shortcuts.push_back(std::make_pair(alt + _L("Left click"), _L("Remove connector from selection"))); + m_shortcuts.push_back(std::make_pair(ctrl + "A", _L("Select all connectors"))); + + init_connector_shapes(); return true; } @@ -187,14 +384,33 @@ std::string GLGizmoAdvancedCut::on_get_name() const return (_(L("Cut"))).ToUTF8().data(); } +void GLGizmoAdvancedCut::on_load(cereal::BinaryInputArchive &ar) +{ + ar(m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_connectors_editing, + m_cut_plane_points[0], m_cut_plane_points[1], m_cut_plane_points[2], m_cut_plane_points[3]); + + m_parent.request_extra_frame(); +} + +void GLGizmoAdvancedCut::on_save(cereal::BinaryOutputArchive &ar) const +{ + ar(m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_connectors_editing, + m_cut_plane_points[0], m_cut_plane_points[1], m_cut_plane_points[2], m_cut_plane_points[3]); +} + void GLGizmoAdvancedCut::on_set_state() { GLGizmoRotate3D::on_set_state(); // Reset m_cut_z on gizmo activation if (get_state() == On) { + m_connectors_editing = false; reset_cut_plane(); } + else if (get_state() == Off) { + clear_selection(); + m_c->object_clipper()->release(); + } } bool GLGizmoAdvancedCut::on_is_activable() const @@ -203,6 +419,14 @@ bool GLGizmoAdvancedCut::on_is_activable() const return selection.is_single_full_instance() && !selection.is_wipe_tower(); } +CommonGizmosDataID GLGizmoAdvancedCut::on_get_requirements() const +{ + return CommonGizmosDataID(int(CommonGizmosDataID::SelectionInfo) + | int(CommonGizmosDataID::InstancesHider) + | int(CommonGizmosDataID::Raycaster) + | int(CommonGizmosDataID::ObjectClipper)); +} + void GLGizmoAdvancedCut::on_start_dragging() { for (auto gizmo : m_gizmos) { @@ -220,6 +444,18 @@ void GLGizmoAdvancedCut::on_start_dragging() m_start_movement = m_movement; m_start_height = m_height; m_drag_pos = m_move_grabber.center; + + if (m_hover_id >= c_connectors_group_id) + Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Move connector"); +} + +void GLGizmoAdvancedCut::on_stop_dragging() +{ + if (m_hover_id == X || m_hover_id == Y || m_hover_id == Z) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Rotate cut plane"); + } else if (m_hover_id == c_connectors_group_id - 1) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Move cut plane"); + } } void GLGizmoAdvancedCut::on_update(const UpdateData& data) @@ -243,129 +479,32 @@ void GLGizmoAdvancedCut::on_update(const UpdateData& data) Vec3d plane_normal = get_plane_normal(); m_height = m_start_height + plane_normal(2) * move; } + + // dragging connectors + if (m_connectors_editing && m_hover_id >= c_connectors_group_id) { + CutConnectors &connectors = m_c->selection_info()->model_object()->cut_connectors; + Vec3d pos; + Vec3d pos_world; + + if (unproject_on_cut_plane(data.mouse_pos.cast(), pos, pos_world)) { + connectors[m_hover_id - c_connectors_group_id].pos = pos; + } + } } void GLGizmoAdvancedCut::on_render() { - const Selection& selection = m_parent.get_selection(); - const BoundingBoxf3& box = selection.get_bounding_box(); - // box center is the coord of object in the world coordinate - Vec3d object_offset = box.center(); - // plane points is in object coordinate - Vec3d plane_center = get_plane_center(); - - // rotate plane - std::array plane_points_rot; - { - for (int i = 0; i < plane_points_rot.size(); i++) { - plane_points_rot[i] = m_cut_plane_points[i] - plane_center; - } - - if (m_rotation(0) > EPSILON) - rotate_x_3d(plane_points_rot, m_rotation(0)); - if (m_rotation(1) > EPSILON) - rotate_y_3d(plane_points_rot, m_rotation(1)); - if (m_rotation(2) > EPSILON) - rotate_z_3d(plane_points_rot, m_rotation(2)); - - for (int i = 0; i < plane_points_rot.size(); i++) { - plane_points_rot[i] += plane_center; - } + update_clipper(); + if (m_connectors_editing) { + render_connectors(); + render_clipper_cut(); + } + else if(!m_connectors_editing) { + check_conflict_for_all_connectors(); + render_cut_plane_and_grabbers(); } - // move plane - Vec3d plane_normal_rot = calc_plane_normal(plane_points_rot); - for (int i = 0; i < plane_points_rot.size(); i++) { - plane_points_rot[i] += plane_normal_rot * m_movement; - } - - // transfer from object coordindate to the world coordinate - for (Vec3d& point : plane_points_rot) { - point += object_offset; - } - - // draw plane - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glDisable(GL_CULL_FACE)); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - - ::glBegin(GL_QUADS); - ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); - for (const Vec3d& point : plane_points_rot) { - ::glVertex3f(point(0), point(1), point(2)); - } - glsafe(::glEnd()); - - glsafe(::glEnable(GL_CULL_FACE)); - glsafe(::glDisable(GL_BLEND)); - - // Draw the grabber and the connecting line - Vec3d plane_center_rot = calc_plane_center(plane_points_rot); - m_move_grabber.center = plane_center_rot + plane_normal_rot * Offset; - //m_move_grabber.angles = m_current_base_rotation + m_rotation; - - glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f)); - glsafe(::glColor3f(1.0, 1.0, 0.0)); - glLineStipple(1, 0x0FFF); - glEnable(GL_LINE_STIPPLE); - ::glBegin(GL_LINES); - ::glVertex3dv(plane_center_rot.data()); - ::glVertex3dv(m_move_grabber.center.data()); - glsafe(::glEnd()); - glDisable(GL_LINE_STIPPLE); - - //std::copy(std::begin(GrabberColor), std::end(GrabberColor), m_move_grabber.color); - //m_move_grabber.color = GrabberColor; - //m_move_grabber.hover_color = GrabberHoverColor; - //m_move_grabber.render(m_hover_id == get_group_id(), (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0)); - bool hover = (m_hover_id == get_group_id()); - std::array render_color; - if (hover) { - render_color = GrabberHoverColor; - } - else - render_color = GrabberColor; - - const GLModel& cube = m_move_grabber.get_cube(); - //BBS set to fixed size grabber - //float fullsize = 2 * (dragging ? get_dragging_half_size(size) : get_half_size(size)); - float fullsize = 8.0f; - if (GLGizmoBase::INV_ZOOM > 0) { - fullsize = m_move_grabber.FixedGrabberSize * GLGizmoBase::INV_ZOOM; - } - - const_cast(&cube)->set_color(-1, render_color); - - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_move_grabber.center.x(), m_move_grabber.center.y(), m_move_grabber.center.z())); - - if (m_rotation(0) > EPSILON) - glsafe(::glRotated(Geometry::rad2deg(m_rotation(0)), 1.0, 0.0, 0.0)); - if (m_rotation(1) > EPSILON) - glsafe(::glRotated(Geometry::rad2deg(m_rotation(1)), 0.0, 1.0, 0.0)); - if (m_rotation(2) > EPSILON) - glsafe(::glRotated(Geometry::rad2deg(m_rotation(2)), 0.0, 0.0, 1.0)); - for (int index = 0; index < m_rotate_cmds.size(); index ++) - { - Rotate_data& data = m_rotate_cmds[index]; - if (data.ax == X) - glsafe(::glRotated(Geometry::rad2deg(data.angle), 1.0, 0.0, 0.0)); - else if (data.ax == Y) - glsafe(::glRotated(Geometry::rad2deg(data.angle), 0.0, 1.0, 0.0)); - else if (data.ax == Z) - glsafe(::glRotated(Geometry::rad2deg(data.angle), 0.0, 0.0, 1.0)); - } - //glsafe(::glRotated(Geometry::rad2deg(angles.z()), 0.0, 0.0, 1.0)); - //glsafe(::glRotated(Geometry::rad2deg(angles.y()), 0.0, 1.0, 0.0)); - //glsafe(::glRotated(Geometry::rad2deg(angles.x()), 1.0, 0.0, 0.0)); - glsafe(::glScaled(fullsize, fullsize, fullsize)); - cube.render(); - glsafe(::glPopMatrix()); - - // Should be placed at last, because GLGizmoRotate3D clears depth buffer - GLGizmoRotate3D::on_render(); + render_cut_line(); } void GLGizmoAdvancedCut::on_render_for_picking() @@ -387,171 +526,95 @@ void GLGizmoAdvancedCut::on_render_for_picking() m_move_grabber.color[2] = color[2]; m_move_grabber.color[3] = color[3]; m_move_grabber.render_for_picking(mean_size); + + glsafe(::glEnable(GL_DEPTH_TEST)); + auto inst_id = m_c->selection_info()->get_active_instance(); + if (inst_id < 0) + return; + + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[inst_id]; + const Vec3d & instance_offset = mi->get_offset(); + const double sla_shift = double(m_c->selection_info()->get_sla_shift()); + + const CutConnectors &connectors = mo->cut_connectors; + for (int i = 0; i < connectors.size(); ++i) { + CutConnector connector = connectors[i]; + Vec3d pos = connector.pos + instance_offset + sla_shift * Vec3d::UnitZ(); + float height = connector.height; + + const Camera &camera = wxGetApp().plater()->get_camera(); + if (connector.attribs.type == CutConnectorType::Dowel && connector.attribs.style == CutConnectorStyle::Prizm) { + pos -= height * m_cut_plane_normal; + height *= 2; + } else if (!is_looking_forward()) + pos -= 0.05 * m_cut_plane_normal; + + Transform3d translate_tf = Transform3d::Identity(); + translate_tf.translate(pos); + + Transform3d scale_tf = Transform3d::Identity(); + scale_tf.scale(Vec3f(connector.radius, connector.radius, height).cast()); + + const Transform3d view_model_matrix = camera.get_view_matrix() * translate_tf * m_rotate_matrix * scale_tf; + + + std::array color = picking_color_component(i+1); + render_connector_model(m_shapes[connectors[i].attribs], color, view_model_matrix); + } } void GLGizmoAdvancedCut::on_render_input_window(float x, float y, float bottom_limit) { - //float unit_size = m_imgui->get_style_scaling() * 48.0f; - float space_size = m_imgui->get_style_scaling() * 8; - float movement_cap = m_imgui->calc_text_size(_L("Movement:")).x; - float rotate_cap = m_imgui->calc_text_size(_L("Rotate")).x; - float caption_size = std::max(movement_cap, rotate_cap) + 2 * space_size; - bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; - unsigned int current_active_id = ImGui::GetActiveID(); - - Vec3d rotation = {Geometry::rad2deg(m_rotation(0)), Geometry::rad2deg(m_rotation(1)), Geometry::rad2deg(m_rotation(2))}; - char buf[3][64]; - float buf_size[3]; - float vec_max = 0, unit_size = 0; - for (int i = 0; i < 3; i++) { - ImGui::DataTypeFormatString(buf[i], IM_ARRAYSIZE(buf[i]), ImGuiDataType_Double, (void *) &rotation[i], "%.2f"); - buf_size[i] = ImGui::CalcTextSize(buf[i]).x; - vec_max = std::max(buf_size[i], vec_max); - } - - float buf_size_max = ImGui::CalcTextSize("-100.00").x ; - if (vec_max < buf_size_max){ - unit_size = buf_size_max + ImGui::GetStyle().FramePadding.x * 2.0f; - } else { - unit_size = vec_max + ImGui::GetStyle().FramePadding.x * 2.0f; - } - GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f); - ImGuiWrapper::push_toolbar_style(m_parent.get_scale()); + GizmoImguiBegin(on_get_name(), + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); - GizmoImguiBegin(on_get_name(), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); - - ImGui::PushItemWidth(caption_size); - ImGui::Dummy(ImVec2(caption_size, -1)); - ImGui::SameLine(caption_size + 1 * space_size); - ImGui::PushItemWidth(unit_size); - ImGui::TextAlignCenter("X"); - ImGui::SameLine(caption_size + 1 * unit_size + 2 * space_size); - ImGui::PushItemWidth(unit_size); - ImGui::TextAlignCenter("Y"); - ImGui::SameLine(caption_size + 2 * unit_size + 3 * space_size); - ImGui::PushItemWidth(unit_size); - ImGui::TextAlignCenter("Z"); - - ImGui::AlignTextToFramePadding(); - - // Rotation input box - ImGui::PushItemWidth(caption_size); - m_imgui->text(_L("Rotation") + " "); - ImGui::SameLine(caption_size + 1 * space_size); - ImGui::PushItemWidth(unit_size); - ImGui::BBLInputDouble("##cut_rotation_x", &rotation[0], 0.0f, 0.0f, "%.2f"); - ImGui::SameLine(caption_size + 1 * unit_size + 2 * space_size); - ImGui::PushItemWidth(unit_size); - ImGui::BBLInputDouble("##cut_rotation_y", &rotation[1], 0.0f, 0.0f, "%.2f"); - ImGui::SameLine(caption_size + 2 * unit_size + 3 * space_size); - ImGui::PushItemWidth(unit_size); - ImGui::BBLInputDouble("##cut_rotation_z", &rotation[2], 0.0f, 0.0f, "%.2f"); - if (current_active_id != m_last_active_id) { - if (std::abs(Geometry::rad2deg(m_rotation(0)) - m_buffered_rotation(0)) > EPSILON || - std::abs(Geometry::rad2deg(m_rotation(1)) - m_buffered_rotation(1)) > EPSILON || - std::abs(Geometry::rad2deg(m_rotation(2)) - m_buffered_rotation(2)) > EPSILON) - { - m_rotation = m_buffered_rotation; - m_buffered_rotation.setZero(); - update_plane_points(); - m_parent.post_event(SimpleEvent(wxEVT_PAINT)); - } - } - else { - m_buffered_rotation(0) = Geometry::deg2rad(rotation(0)); - m_buffered_rotation(1) = Geometry::deg2rad(rotation(1)); - m_buffered_rotation(2) = Geometry::deg2rad(rotation(2)); + if (m_connectors_editing) { + init_connectors_input_window_data(); + render_connectors_input_window(x, y, bottom_limit); } + else + render_cut_plane_input_window(x, y, bottom_limit); - ImGui::Separator(); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f, 10.0f)); - // Movement input box - double movement = m_movement; - ImGui::PushItemWidth(caption_size); - ImGui::AlignTextToFramePadding(); - m_imgui->text(_L("Movement") + " "); - ImGui::SameLine(caption_size + 1 * space_size); - ImGui::PushItemWidth(3 * unit_size + 2 * space_size); - ImGui::BBLInputDouble("##cut_movement", &movement, 0.0f, 0.0f, "%.2f"); - if (current_active_id != m_last_active_id) { - if (std::abs(m_buffered_movement - m_movement) > EPSILON) { - m_movement = m_buffered_movement; - m_buffered_movement = 0.0; + render_input_window_warning(); - // update absolute height - Vec3d plane_normal = get_plane_normal(); - m_height_delta = plane_normal(2) * m_movement; - m_height += m_height_delta; - m_buffered_height = m_height; - - update_plane_points(); - m_parent.post_event(SimpleEvent(wxEVT_PAINT)); - } - } else { - m_buffered_movement = movement; - } - - // height input box - double height = m_height; - ImGui::PushItemWidth(caption_size); - ImGui::AlignTextToFramePadding(); - // only allow setting height when cut plane is horizontal - Vec3d plane_normal = get_plane_normal(); - m_imgui->disabled_begin(std::abs(plane_normal(0)) > EPSILON || std::abs(plane_normal(1)) > EPSILON); - m_imgui->text(_L("Height") + " "); - ImGui::SameLine(caption_size + 1 * space_size); - ImGui::PushItemWidth(3 * unit_size + 2 * space_size); - ImGui::BBLInputDouble("##cut_height", &height, 0.0f, 0.0f, "%.2f"); - if (current_active_id != m_last_active_id) { - if (std::abs(m_buffered_height - m_height) > EPSILON) { - m_height_delta = m_buffered_height - m_height; - m_height = m_buffered_height; - update_plane_points(); - m_parent.post_event(SimpleEvent(wxEVT_PAINT)); - } - } - else { - m_buffered_height = height; - } - ImGui::PopStyleVar(1); - m_imgui->disabled_end(); - ImGui::Separator(); - // Part selection - m_imgui->bbl_checkbox(_L("Keep upper part"), m_keep_upper); - m_imgui->bbl_checkbox(_L("Keep lower part"), m_keep_lower); - m_imgui->bbl_checkbox(_L("Cut to parts"), m_cut_to_parts); - -#if 0 - // Auto segment input - ImGui::PushItemWidth(m_imgui->get_style_scaling() * 150.0); - m_imgui->checkbox(_L("Auto Segment"), m_do_segment); - m_imgui->disabled_begin(!m_do_segment); - ImGui::InputDouble("smoothing_alpha", &m_segment_smoothing_alpha, 0.0f, 0.0f, "%.2f"); - m_segment_smoothing_alpha = std::max(0.1, std::min(100.0, m_segment_smoothing_alpha)); - ImGui::InputInt("segment number", &m_segment_number); - m_segment_number = std::max(1, m_segment_number); - m_imgui->disabled_end(); - - ImGui::Separator(); -#endif - - // Cut button - m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower && !m_cut_to_parts && !m_do_segment)); - const bool cut_clicked = m_imgui->button(_L("Perform cut")); - m_imgui->disabled_end(); - ImGui::SameLine(); - const bool reset_clicked = m_imgui->button(_L("Reset")); - if (reset_clicked) { reset_all(); } GizmoImguiEnd(); ImGuiWrapper::pop_toolbar_style(); +} - // Perform cut - if (cut_clicked && (m_keep_upper || m_keep_lower || m_cut_to_parts || m_do_segment)) - perform_cut(m_parent.get_selection()); +void GLGizmoAdvancedCut::show_tooltip_information(float x, float y) +{ + float caption_max = 0.f; + for (const auto& short_cut : m_shortcuts) { + caption_max = std::max(caption_max, m_imgui->calc_text_size(short_cut.first).x); + } - m_last_active_id = current_active_id; + ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); + ImTextureID hover_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); + + caption_max += m_imgui->calc_text_size(": ").x + 35.f; + + float font_size = ImGui::GetFontSize(); + ImVec2 button_size = ImVec2(font_size * 1.8, font_size * 1.3); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, ImGui::GetStyle().FramePadding.y}); + ImGui::ImageButton3(normal_id, hover_id, button_size); + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip2(ImVec2(x, y)); + auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) { + m_imgui->text_colored(ImGuiWrapper::COL_ACTIVE, caption); + ImGui::SameLine(caption_max); + m_imgui->text_colored(ImGuiWrapper::COL_WINDOW_BG, text); + }; + + for (const auto& short_cut : m_shortcuts) + draw_text_with_caption(short_cut.first + ": ", short_cut.second); + ImGui::EndTooltip(); + } + ImGui::PopStyleVar(2); } void GLGizmoAdvancedCut::set_movement(double movement) const @@ -561,6 +624,9 @@ void GLGizmoAdvancedCut::set_movement(double movement) const void GLGizmoAdvancedCut::perform_cut(const Selection& selection) { + if (!can_perform_cut()) + return; + const int instance_idx = selection.get_instance_idx(); const int object_idx = selection.get_object_idx(); @@ -569,17 +635,86 @@ void GLGizmoAdvancedCut::perform_cut(const Selection& selection) // m_cut_z is the distance from the bed. Subtract possible SLA elevation. const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); - // BBS: do segment - if (m_do_segment) + // perform cut { - wxGetApp().plater()->segment(object_idx, instance_idx, m_segment_smoothing_alpha, m_segment_number); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Cut by Plane"); + + ModelObject *mo = wxGetApp().plater()->model().objects[object_idx]; + const bool has_connectors = !mo->cut_connectors.empty(); + + bool create_dowels_as_separate_object = false; + // update connectors pos as offset of its center before cut performing + apply_connectors_in_model(mo, create_dowels_as_separate_object); + + // BBS: do segment + if (m_do_segment) { + wxGetApp().plater()->segment(object_idx, instance_idx, m_segment_smoothing_alpha, m_segment_number); + } else { + wxGetApp().plater()->cut(object_idx, instance_idx, get_plane_points_world_coord(), + only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) | + only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) | + only_if(m_place_on_cut_upper, ModelObjectCutAttribute::PlaceOnCutUpper) | + only_if(m_place_on_cut_lower, ModelObjectCutAttribute::PlaceOnCutLower) | + only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) | + only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) | + only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels)); + } } - else { - wxGetApp().plater()->cut(object_idx, instance_idx, get_plane_points_world_coord(), - only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) | - only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) | - only_if(m_cut_to_parts, ModelObjectCutAttribute::CutToParts)); +} + +bool GLGizmoAdvancedCut::can_perform_cut() const +{ + if (m_has_invalid_connector || (!m_keep_upper && !m_keep_lower) || m_connectors_editing) + return false; + + return true; + //const auto clipper = m_c->object_clipper(); + //return clipper && clipper->has_valid_contour(); +} + +void GLGizmoAdvancedCut::apply_connectors_in_model(ModelObject *mo, bool &create_dowels_as_separate_object) +{ + clear_selection(); + + for (CutConnector &connector : mo->cut_connectors) { + connector.rotation_m = m_rotate_matrix; + + if (connector.attribs.type == CutConnectorType::Dowel) { + if (connector.attribs.style == CutConnectorStyle::Prizm) connector.height *= 2; + create_dowels_as_separate_object = true; + } else { + // culculate shift of the connector center regarding to the position on the cut plane + Vec3d shifted_center = m_cut_plane_center + Vec3d::UnitZ(); + shifted_center = rotate_vec3d_around_vec3d_with_rotate_matrix(shifted_center, m_cut_plane_center, m_rotate_matrix); + Vec3d norm = (shifted_center - m_cut_plane_center).normalized(); + connector.pos += norm * 0.5 * double(connector.height); + } } + + mo->apply_cut_connectors(_u8L("Connector")); +} + +bool GLGizmoAdvancedCut::is_selection_changed(bool alt_down, bool shift_down) +{ + if (m_hover_id >= c_connectors_group_id) { + if (alt_down) + select_connector(m_hover_id - c_connectors_group_id, false); + else { + if (!shift_down) unselect_all_connectors(); + select_connector(m_hover_id - c_connectors_group_id, true); + } + return true; + } + return false; +} + +void GLGizmoAdvancedCut::select_connector(int idx, bool select) +{ + m_selected[idx] = select; + if (select) + ++m_selected_count; + else + --m_selected_count; } Vec3d GLGizmoAdvancedCut::calc_plane_normal(const std::array& plane_points) const @@ -630,5 +765,1061 @@ void GLGizmoAdvancedCut::finish_rotation() update_plane_points(); } + +void GLGizmoAdvancedCut::put_connectors_on_cut_plane(const Vec3d &cp_normal, double cp_offset) +{ + ModelObject *mo = m_c->selection_info()->model_object(); + if (CutConnectors &connectors = mo->cut_connectors; !connectors.empty()) { + const float sla_shift = m_c->selection_info()->get_sla_shift(); + const Vec3d &instance_offset = mo->instances[m_c->selection_info()->get_active_instance()]->get_offset(); + + for (auto &connector : connectors) { + // convert connetor pos to the world coordinates + Vec3d pos = connector.pos + instance_offset; + pos[Z] += sla_shift; + // scalar distance from point to plane along the normal + double distance = -cp_normal.dot(pos) + cp_offset; + // move connector + connector.pos += distance * cp_normal; + } + } +} + +void GLGizmoAdvancedCut::update_clipper() +{ + BoundingBoxf3 box = bounding_box(); + double radius = box.radius(); + Vec3d plane_center = m_cut_plane_center; + + Vec3d begin, end = begin = plane_center; + begin[Z] = box.center().z() - radius; + end[Z] = box.center().z() + radius; + + double phi; + Vec3d rotation_axis; + Matrix3d rotation_matrix; + Geometry::rotation_from_two_vectors(Vec3d::UnitZ(), m_cut_plane_normal, rotation_axis, phi, &rotation_matrix); + + m_rotate_matrix.setIdentity(); + m_rotate_matrix = rotation_matrix * m_rotate_matrix; + + begin = rotate_vec3d_around_vec3d_with_rotate_matrix(begin, plane_center, m_rotate_matrix); + end = rotate_vec3d_around_vec3d_with_rotate_matrix(end, plane_center, m_rotate_matrix); + + Vec3d normal = end - begin; + + if (!is_looking_forward()) { + end = begin = plane_center; + begin[Z] = box.center().z() + radius; + end[Z] = box.center().z() - radius; + + begin = rotate_vec3d_around_vec3d_with_rotate_matrix(begin, plane_center, m_rotate_matrix); + end = rotate_vec3d_around_vec3d_with_rotate_matrix(end, plane_center, m_rotate_matrix); + + // recalculate normal for clipping plane, if camera is looking downward to cut plane + normal = end - begin; + if (normal == Vec3d::Zero()) + return; + } + + // calculate normal and offset for clipping plane + double dist = (plane_center - begin).norm(); + dist = std::clamp(dist, 0.0001, normal.norm()); + normal.normalize(); + const double offset = normal.dot(begin) + dist; + + m_c->object_clipper()->set_range_and_pos(normal, offset, dist); + + put_connectors_on_cut_plane(normal, offset); +} + +void GLGizmoAdvancedCut::render_cut_plane_and_grabbers() +{ + const Selection & selection = m_parent.get_selection(); + const BoundingBoxf3 &box = selection.get_bounding_box(); + // box center is the coord of object in the world coordinate + Vec3d object_offset = box.center(); + // plane points is in object coordinate + Vec3d plane_center = get_plane_center(); + + m_cut_plane_center = object_offset + plane_center; + + // rotate plane + std::array plane_points_rot; + { + for (int i = 0; i < plane_points_rot.size(); i++) { + plane_points_rot[i] = m_cut_plane_points[i] - plane_center; + } + + if (m_rotation(0) > EPSILON) + rotate_x_3d(plane_points_rot, m_rotation(0)); + if (m_rotation(1) > EPSILON) + rotate_y_3d(plane_points_rot, m_rotation(1)); + if (m_rotation(2) > EPSILON) + rotate_z_3d(plane_points_rot, m_rotation(2)); + + for (int i = 0; i < plane_points_rot.size(); i++) { + plane_points_rot[i] += plane_center; + } + } + + // move plane + Vec3d plane_normal_rot = calc_plane_normal(plane_points_rot); + m_cut_plane_normal = plane_normal_rot; + for (int i = 0; i < plane_points_rot.size(); i++) { + plane_points_rot[i] += plane_normal_rot * m_movement; + } + + // transfer from object coordindate to the world coordinate + for (Vec3d& point : plane_points_rot) { + point += object_offset; + } + + // draw plane + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_CULL_FACE)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + ::glBegin(GL_QUADS); + ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); + for (const Vec3d& point : plane_points_rot) { + ::glVertex3f(point(0), point(1), point(2)); + } + glsafe(::glEnd()); + + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glDisable(GL_BLEND)); + + // Draw the grabber and the connecting line + Vec3d plane_center_rot = calc_plane_center(plane_points_rot); + m_move_grabber.center = plane_center_rot + plane_normal_rot * Offset; + // m_move_grabber.angles = m_current_base_rotation + m_rotation; + + glsafe(::glDisable(GL_DEPTH_TEST)); + glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f)); + glsafe(::glColor3f(1.0, 1.0, 0.0)); + glLineStipple(1, 0x0FFF); + glEnable(GL_LINE_STIPPLE); + ::glBegin(GL_LINES); + ::glVertex3dv(plane_center_rot.data()); + ::glVertex3dv(m_move_grabber.center.data()); + glsafe(::glEnd()); + glDisable(GL_LINE_STIPPLE); + + // std::copy(std::begin(GrabberColor), std::end(GrabberColor), m_move_grabber.color); + // m_move_grabber.color = GrabberColor; + // m_move_grabber.hover_color = GrabberHoverColor; + // m_move_grabber.render(m_hover_id == get_group_id(), (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0)); + bool hover = (m_hover_id == get_group_id()); + std::array render_color; + if (hover) { + render_color = GrabberHoverColor; + } + else + render_color = GrabberColor; + + const GLModel &cube = m_move_grabber.get_cube(); + // BBS set to fixed size grabber + // float fullsize = 2 * (dragging ? get_dragging_half_size(size) : get_half_size(size)); + float fullsize = 8.0f; + if (GLGizmoBase::INV_ZOOM > 0) { + fullsize = m_move_grabber.FixedGrabberSize * GLGizmoBase::INV_ZOOM; + } + + const_cast(&cube)->set_color(-1, render_color); + + glsafe(::glPushMatrix()); + glsafe(::glTranslated(m_move_grabber.center.x(), m_move_grabber.center.y(), m_move_grabber.center.z())); + glsafe(::glMultMatrixd(m_rotate_matrix.data())); + + glsafe(::glScaled(fullsize, fullsize, fullsize)); + cube.render(); + glsafe(::glPopMatrix()); + + // Should be placed at last, because GLGizmoRotate3D clears depth buffer + set_center(m_cut_plane_center); + GLGizmoRotate3D::on_render(); +} + +void GLGizmoAdvancedCut::render_connectors() +{ + ::glEnable(GL_DEPTH_TEST); + + const ModelObject *mo = m_c->selection_info()->model_object(); + auto inst_id = m_c->selection_info()->get_active_instance(); + if (inst_id < 0) + return; + + const CutConnectors &connectors = mo->cut_connectors; + if (connectors.size() != m_selected.size()) { + clear_selection(); + m_selected.resize(connectors.size(), false); + } + + const ModelInstance *mi = mo->instances[inst_id]; + const Vec3d & instance_offset = mi->get_offset(); + const double sla_shift = double(m_c->selection_info()->get_sla_shift()); + + m_has_invalid_connector = false; + m_info_stats.invalidate(); + + ColorRGBA render_color = CONNECTOR_DEF_COLOR; + for (size_t i = 0; i < connectors.size(); ++i) { + const CutConnector &connector = connectors[i]; + + float height = connector.height; + // recalculate connector position to world position + Vec3d pos = connector.pos + instance_offset + sla_shift * Vec3d::UnitZ(); + + // First decide about the color of the point. + const bool conflict_connector = is_conflict_for_connector(i, connectors, pos); + if (conflict_connector) { + m_has_invalid_connector = true; + render_color = CONNECTOR_ERR_COLOR; + } else // default connector color + render_color = connector.attribs.type == CutConnectorType::Dowel ? DOWEL_COLOR : PLAG_COLOR; + + if (!m_connectors_editing) + render_color = CONNECTOR_ERR_COLOR; + else if (size_t(m_hover_id - 4) == i) + render_color = conflict_connector ? HOVERED_ERR_COLOR : connector.attribs.type == CutConnectorType::Dowel ? HOVERED_DOWEL_COLOR : HOVERED_PLAG_COLOR; + else if (m_selected[i]) + render_color = connector.attribs.type == CutConnectorType::Dowel ? SELECTED_DOWEL_COLOR : SELECTED_PLAG_COLOR; + + const Camera &camera = wxGetApp().plater()->get_camera(); + if (connector.attribs.type == CutConnectorType::Dowel && connector.attribs.style == CutConnectorStyle::Prizm) { + pos -= height * m_cut_plane_normal; + height *= 2; + } else if (!is_looking_forward()) + pos -= 0.05 * m_cut_plane_normal; + + Transform3d translate_tf = Transform3d::Identity(); + translate_tf.translate(pos); + + Transform3d scale_tf = Transform3d::Identity(); + scale_tf.scale(Vec3f(connector.radius, connector.radius, height).cast()); + + const Transform3d view_model_matrix = camera.get_view_matrix() * translate_tf * m_rotate_matrix * scale_tf; + + //render_color = {1.f, 0.f, 0.f, 1.f}; + render_connector_model(m_shapes[connector.attribs], render_color, view_model_matrix); + } +} + +void GLGizmoAdvancedCut::render_clipper_cut() +{ + m_c->object_clipper()->render_cut(); +} + +void GLGizmoAdvancedCut::render_cut_line() +{ + if (!cut_line_processing() || m_cut_line_end == Vec3d::Zero()) + return; + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + glsafe(::glColor3f(0.0, 1.0, 0.0)); + glEnable(GL_LINE_STIPPLE); + ::glBegin(GL_LINES); + ::glVertex3dv(m_cut_line_begin.data()); + ::glVertex3dv(m_cut_line_end.data()); + glsafe(::glEnd()); + glDisable(GL_LINE_STIPPLE); +} + +void GLGizmoAdvancedCut::render_connector_model(GLModel &model, const std::array& color, Transform3d view_model_matrix) +{ + GLShaderProgram *shader = wxGetApp().get_shader("gouraud_light_uniform"); + if (shader) { + shader->start_using(); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", wxGetApp().plater()->get_camera().get_projection_matrix()); + + model.set_color(-1, color); + model.render(); + + shader->stop_using(); + } +} + +void GLGizmoAdvancedCut::clear_selection() +{ + m_selected.clear(); + m_selected_count = 0; +} + +void GLGizmoAdvancedCut::init_connector_shapes() +{ + for (const CutConnectorType &type : {CutConnectorType::Dowel, CutConnectorType::Plug}) + for (const CutConnectorStyle &style : {CutConnectorStyle::Frustum, CutConnectorStyle::Prizm}) + for (const CutConnectorShape &shape : {CutConnectorShape::Circle, CutConnectorShape::Hexagon, CutConnectorShape::Square, CutConnectorShape::Triangle}) { + const CutConnectorAttributes attribs = {type, style, shape}; + const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs); + m_shapes[attribs].init_from(its); + } +} + +void GLGizmoAdvancedCut::set_connectors_editing(bool connectors_editing) +{ + m_connectors_editing = connectors_editing; + // todo: zhimin need a better method + // after change render mode, need update for scene + on_render(); +} + +void GLGizmoAdvancedCut::reset_connectors() +{ + m_c->selection_info()->model_object()->cut_connectors.clear(); + clear_selection(); +} + +void GLGizmoAdvancedCut::update_connector_shape() +{ + CutConnectorAttributes attribs = {m_connector_type, CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id)}; + + const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs); + m_connector_mesh.clear(); + m_connector_mesh = TriangleMesh(its); +} + +void GLGizmoAdvancedCut::apply_selected_connectors(std::function apply_fn) +{ + for (size_t idx = 0; idx < m_selected.size(); idx++) + if (m_selected[idx]) + apply_fn(idx); +} + +void GLGizmoAdvancedCut::select_all_connectors() +{ + std::fill(m_selected.begin(), m_selected.end(), true); + m_selected_count = int(m_selected.size()); +} + +void GLGizmoAdvancedCut::unselect_all_connectors() +{ + std::fill(m_selected.begin(), m_selected.end(), false); + m_selected_count = 0; + validate_connector_settings(); +} + +void GLGizmoAdvancedCut::validate_connector_settings() +{ + if (m_connector_depth_ratio < 0.f) + m_connector_depth_ratio = 3.f; + if (m_connector_depth_ratio_tolerance < 0.f) + m_connector_depth_ratio_tolerance = 0.1f; + if (m_connector_size < 0.f) + m_connector_size = 2.5f; + if (m_connector_size_tolerance < 0.f) + m_connector_size_tolerance = 0.f; + + if (m_connector_type == CutConnectorType::Undef) + m_connector_type = CutConnectorType::Plug; + if (m_connector_style == size_t(CutConnectorStyle::Undef)) + m_connector_style = size_t(CutConnectorStyle::Prizm); + if (m_connector_shape_id == size_t(CutConnectorShape::Undef)) + m_connector_shape_id = size_t(CutConnectorShape::Circle); +} + +bool GLGizmoAdvancedCut::add_connector(CutConnectors &connectors, const Vec2d &mouse_position) +{ + if (!m_connectors_editing) + return false; + + Vec3d pos; + Vec3d pos_world; + if (unproject_on_cut_plane(mouse_position.cast(), pos, pos_world)) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Add connector"); + unselect_all_connectors(); + + connectors.emplace_back(pos, m_rotate_matrix, m_connector_size * 0.5f, m_connector_depth_ratio, m_connector_size_tolerance, m_connector_depth_ratio_tolerance, + CutConnectorAttributes(CutConnectorType(m_connector_type), CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id))); + m_selected.push_back(true); + m_selected_count = 1; + assert(m_selected.size() == connectors.size()); + m_parent.set_as_dirty(); + + return true; + } + return false; +} + +bool GLGizmoAdvancedCut::delete_selected_connectors() +{ + CutConnectors &connectors = m_c->selection_info()->model_object()->cut_connectors; + if (connectors.empty()) + return false; + + Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Delete connector"); + + // remove connectors + for (int i = int(connectors.size()) - 1; i >= 0; i--) + if (m_selected[i]) connectors.erase(connectors.begin() + i); + // remove selections + m_selected.erase(std::remove_if(m_selected.begin(), m_selected.end(), + [](const auto &selected) { + return selected;}), + m_selected.end()); + + m_selected_count = 0; + + assert(m_selected.size() == connectors.size()); + m_parent.set_as_dirty(); + return true; +} + +bool GLGizmoAdvancedCut::is_outside_of_cut_contour(size_t idx, const CutConnectors &connectors, const Vec3d cur_pos) +{ + // check if connector pos is out of clipping plane + if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(cur_pos)) { + m_info_stats.outside_cut_contour++; + return true; + } + + // check if connector bottom contour is out of clipping plane + const CutConnector & cur_connector = connectors[idx]; + const CutConnectorShape shape = CutConnectorShape(cur_connector.attribs.shape); + const int sectorCount = shape == CutConnectorShape::Triangle ? 3 : + shape == CutConnectorShape::Square ? 4 : + shape == CutConnectorShape::Circle ? 60: // supposably, 60 points are enough for conflict detection + shape == CutConnectorShape::Hexagon ? 6 : 1 ; + + indexed_triangle_set mesh; + auto & vertices = mesh.vertices; + vertices.reserve(sectorCount + 1); + + float fa = 2 * PI / sectorCount; + auto vec = Eigen::Vector2f(0, cur_connector.radius); + for (float angle = 0; angle < 2.f * PI; angle += fa) { + Vec2f p = Eigen::Rotation2Df(angle) * vec; + vertices.emplace_back(Vec3f(p(0), p(1), 0.f)); + } + + Transform3d transform = Transform3d::Identity(); + transform.translate(cur_pos); + its_transform(mesh, transform * m_rotate_matrix); + + for (auto vertex : vertices) { + if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(vertex.cast())) { + m_info_stats.outside_cut_contour++; + return true; + } + } + + return false; +} + +bool GLGizmoAdvancedCut::is_conflict_for_connector(size_t idx, const CutConnectors &connectors, const Vec3d cur_pos) +{ + if (is_outside_of_cut_contour(idx, connectors, cur_pos)) + return true; + + const CutConnector &cur_connector = connectors[idx]; + + Transform3d translate_tf = Transform3d::Identity(); + translate_tf.translate(cur_pos); + Transform3d scale_tf = Transform3d::Identity(); + scale_tf.scale(Vec3f(cur_connector.radius, cur_connector.radius, cur_connector.height).cast()); + const Transform3d matrix = translate_tf * m_rotate_matrix * scale_tf; + + const BoundingBoxf3 cur_tbb = m_shapes[cur_connector.attribs].get_bounding_box().transformed(matrix); + + // check if connector's bounding box is inside the object's bounding box + if (!bounding_box().contains(cur_tbb)) { + m_info_stats.outside_bb++; + return true; + } + + // check if connectors are overlapping + for (size_t i = 0; i < connectors.size(); ++i) { + if (i == idx) continue; + const CutConnector &connector = connectors[i]; + + if ((connector.pos - cur_connector.pos).norm() < double(connector.radius + cur_connector.radius)) { + m_info_stats.is_overlap = true; + return true; + } + } + + return false; +} + +void GLGizmoAdvancedCut::check_conflict_for_all_connectors() +{ + const ModelObject *mo = m_c->selection_info()->model_object(); + auto inst_id = m_c->selection_info()->get_active_instance(); + if (inst_id < 0) + return; + + const CutConnectors &connectors = mo->cut_connectors; + const ModelInstance *mi = mo->instances[inst_id]; + const Vec3d & instance_offset = mi->get_offset(); + const double sla_shift = double(m_c->selection_info()->get_sla_shift()); + + m_has_invalid_connector = false; + m_info_stats.invalidate(); + + for (size_t i = 0; i < connectors.size(); ++i) { + const CutConnector &connector = connectors[i]; + + Vec3d pos = connector.pos + instance_offset + sla_shift * Vec3d::UnitZ(); + + // First decide about the color of the point. + const bool conflict_connector = is_conflict_for_connector(i, connectors, pos); + if (conflict_connector) { + m_has_invalid_connector = true; + } + } +} + +void GLGizmoAdvancedCut::render_cut_plane_input_window(float x, float y, float bottom_limit) +{ + // float unit_size = m_imgui->get_style_scaling() * 48.0f; + float space_size = m_imgui->get_style_scaling() * 8; + float movement_cap = m_imgui->calc_text_size(_L("Movement:")).x; + float rotate_cap = m_imgui->calc_text_size(_L("Rotate")).x; + float caption_size = std::max(movement_cap, rotate_cap) + 2 * space_size; + bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; + unsigned int current_active_id = ImGui::GetActiveID(); + + Vec3d rotation = {Geometry::rad2deg(m_rotation(0)), Geometry::rad2deg(m_rotation(1)), Geometry::rad2deg(m_rotation(2))}; + char buf[3][64]; + float buf_size[3]; + float vec_max = 0, unit_size = 0; + for (int i = 0; i < 3; i++) { + ImGui::DataTypeFormatString(buf[i], IM_ARRAYSIZE(buf[i]), ImGuiDataType_Double, (void *) &rotation[i], "%.2f"); + buf_size[i] = ImGui::CalcTextSize(buf[i]).x; + vec_max = std::max(buf_size[i], vec_max); + } + + float buf_size_max = ImGui::CalcTextSize("-100.00").x; + if (vec_max < buf_size_max) { + unit_size = buf_size_max + ImGui::GetStyle().FramePadding.x * 2.0f; + } else { + unit_size = vec_max + ImGui::GetStyle().FramePadding.x * 2.0f; + } + + ImGui::PushItemWidth(caption_size); + ImGui::Dummy(ImVec2(caption_size, -1)); + ImGui::SameLine(caption_size + 1 * space_size); + ImGui::PushItemWidth(unit_size); + ImGui::TextAlignCenter("X"); + ImGui::SameLine(caption_size + 1 * unit_size + 2 * space_size); + ImGui::PushItemWidth(unit_size); + ImGui::TextAlignCenter("Y"); + ImGui::SameLine(caption_size + 2 * unit_size + 3 * space_size); + ImGui::PushItemWidth(unit_size); + ImGui::TextAlignCenter("Z"); + + ImGui::AlignTextToFramePadding(); + + // Rotation input box + ImGui::PushItemWidth(caption_size); + m_imgui->text(_L("Rotation") + " "); + ImGui::SameLine(caption_size + 1 * space_size); + ImGui::PushItemWidth(unit_size); + ImGui::BBLInputDouble("##cut_rotation_x", &rotation[0], 0.0f, 0.0f, "%.2f"); + ImGui::SameLine(caption_size + 1 * unit_size + 2 * space_size); + ImGui::PushItemWidth(unit_size); + ImGui::BBLInputDouble("##cut_rotation_y", &rotation[1], 0.0f, 0.0f, "%.2f"); + ImGui::SameLine(caption_size + 2 * unit_size + 3 * space_size); + ImGui::PushItemWidth(unit_size); + ImGui::BBLInputDouble("##cut_rotation_z", &rotation[2], 0.0f, 0.0f, "%.2f"); + if (current_active_id != m_last_active_id) { + if (std::abs(Geometry::rad2deg(m_rotation(0)) - m_buffered_rotation(0)) > EPSILON || std::abs(Geometry::rad2deg(m_rotation(1)) - m_buffered_rotation(1)) > EPSILON || + std::abs(Geometry::rad2deg(m_rotation(2)) - m_buffered_rotation(2)) > EPSILON) { + m_rotation = m_buffered_rotation; + m_buffered_rotation.setZero(); + update_plane_points(); + m_parent.post_event(SimpleEvent(wxEVT_PAINT)); + } + } else { + m_buffered_rotation(0) = Geometry::deg2rad(rotation(0)); + m_buffered_rotation(1) = Geometry::deg2rad(rotation(1)); + m_buffered_rotation(2) = Geometry::deg2rad(rotation(2)); + } + + ImGui::Separator(); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f, 10.0f)); + // Movement input box + double movement = m_movement; + ImGui::PushItemWidth(caption_size); + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("Movement") + " "); + ImGui::SameLine(caption_size + 1 * space_size); + ImGui::PushItemWidth(3 * unit_size + 2 * space_size); + ImGui::BBLInputDouble("##cut_movement", &movement, 0.0f, 0.0f, "%.2f"); + if (current_active_id != m_last_active_id) { + if (std::abs(m_buffered_movement - m_movement) > EPSILON) { + m_movement = m_buffered_movement; + m_buffered_movement = 0.0; + + // update absolute height + Vec3d plane_normal = get_plane_normal(); + m_height_delta = plane_normal(2) * m_movement; + m_height += m_height_delta; + m_buffered_height = m_height; + + update_plane_points(); + m_parent.post_event(SimpleEvent(wxEVT_PAINT)); + } + } else { + m_buffered_movement = movement; + } + + // height input box + double height = m_height; + ImGui::PushItemWidth(caption_size); + ImGui::AlignTextToFramePadding(); + // only allow setting height when cut plane is horizontal + Vec3d plane_normal = get_plane_normal(); + m_imgui->disabled_begin(std::abs(plane_normal(0)) > EPSILON || std::abs(plane_normal(1)) > EPSILON); + m_imgui->text(_L("Height") + " "); + ImGui::SameLine(caption_size + 1 * space_size); + ImGui::PushItemWidth(3 * unit_size + 2 * space_size); + ImGui::BBLInputDouble("##cut_height", &height, 0.0f, 0.0f, "%.2f"); + if (current_active_id != m_last_active_id) { + if (std::abs(m_buffered_height - m_height) > EPSILON) { + m_height_delta = m_buffered_height - m_height; + m_height = m_buffered_height; + update_plane_points(); + m_parent.post_event(SimpleEvent(wxEVT_PAINT)); + } + } else { + m_buffered_height = height; + } + ImGui::PopStyleVar(1); + m_imgui->disabled_end(); + + m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower); + if (m_imgui->button(_L("Add/Edit connectors"))) set_connectors_editing(true); + m_imgui->disabled_end(); + + ImGui::Separator(); + + float label_width = 0; + for (const wxString& label : {_L("Upper part"), _L("Lower part")}) { + const float width = m_imgui->calc_text_size(label).x + m_imgui->scaled(1.5f); + if (label_width < width) + label_width = width; + } + + auto render_part_action_line = [this, label_width](const wxString& label, const wxString& suffix, bool& keep_part, bool& place_on_cut_part, bool& rotate_part) { + CutConnectors &connectors = m_c->selection_info()->model_object()->cut_connectors; + + bool keep = true; + ImGui::AlignTextToFramePadding(); + m_imgui->text(label); + + ImGui::SameLine(label_width); + + m_imgui->disabled_begin(!connectors.empty()); + m_imgui->bbl_checkbox(_L("Keep") + suffix, connectors.empty() ? keep_part : keep); + m_imgui->disabled_end(); + + ImGui::SameLine(); + + m_imgui->disabled_begin(!keep_part); + if (m_imgui->bbl_checkbox(_L("Place on cut") + suffix, place_on_cut_part)) + rotate_part = false; + ImGui::SameLine(); + if (m_imgui->bbl_checkbox(_L("Flip") + suffix, rotate_part)) + place_on_cut_part = false; + m_imgui->disabled_end(); + }; + + m_imgui->text(_L("After cut") + ": "); + render_part_action_line( _L("Upper part"), "##upper", m_keep_upper, m_place_on_cut_upper, m_rotate_upper); + render_part_action_line( _L("Lower part"), "##lower", m_keep_lower, m_place_on_cut_lower, m_rotate_lower); + + +#if 0 + // Auto segment input + ImGui::PushItemWidth(m_imgui->get_style_scaling() * 150.0); + m_imgui->checkbox(_L("Auto Segment"), m_do_segment); + m_imgui->disabled_begin(!m_do_segment); + ImGui::InputDouble("smoothing_alpha", &m_segment_smoothing_alpha, 0.0f, 0.0f, "%.2f"); + m_segment_smoothing_alpha = std::max(0.1, std::min(100.0, m_segment_smoothing_alpha)); + ImGui::InputInt("segment number", &m_segment_number); + m_segment_number = std::max(1, m_segment_number); + m_imgui->disabled_end(); + + ImGui::Separator(); +#endif + + // Cut button + m_imgui->disabled_begin(!can_perform_cut()); + if (m_imgui->button(_L("Perform cut"))) + perform_cut(m_parent.get_selection()); + m_imgui->disabled_end(); + ImGui::SameLine(); + const bool reset_clicked = m_imgui->button(_L("Reset")); + if (reset_clicked) { reset_all(); } + + m_last_active_id = current_active_id; +} + +void GLGizmoAdvancedCut::init_connectors_input_window_data() +{ + CutConnectors &connectors = m_c->selection_info()->model_object()->cut_connectors; + + m_label_width = m_imgui->get_font_size() * 6.f; + m_control_width = m_imgui->get_font_size() * 9.f; + + if (m_connectors_editing && m_selected_count > 0) { + float depth_ratio {UndefFloat}; + float depth_ratio_tolerance {UndefFloat}; + float radius {UndefFloat}; + float radius_tolerance {UndefFloat}; + CutConnectorType type{CutConnectorType::Undef}; + CutConnectorStyle style{CutConnectorStyle::Undef}; + CutConnectorShape shape{CutConnectorShape::Undef}; + + bool is_init = false; + for (size_t idx = 0; idx < m_selected.size(); idx++) + if (m_selected[idx]) { + const CutConnector &connector = connectors[idx]; + if (!is_init) { + depth_ratio = connector.height; + depth_ratio_tolerance = connector.height_tolerance; + radius = connector.radius; + radius_tolerance = connector.radius_tolerance; + type = connector.attribs.type; + style = connector.attribs.style; + shape = connector.attribs.shape; + + if (m_selected_count == 1) break; + is_init = true; + } else { + if (!is_approx(depth_ratio, connector.height)) + depth_ratio = UndefFloat; + if (!is_approx(depth_ratio_tolerance, connector.height_tolerance)) + depth_ratio_tolerance = UndefFloat; + if (!is_approx(radius, connector.radius)) + radius = UndefFloat; + if (!is_approx(radius_tolerance, connector.radius_tolerance)) + radius_tolerance = UndefFloat; + + if (type != connector.attribs.type) + type = CutConnectorType::Undef; + if (style != connector.attribs.style) + style = CutConnectorStyle::Undef; + if (shape != connector.attribs.shape) + shape = CutConnectorShape::Undef; + } + } + + m_connector_depth_ratio = depth_ratio; + m_connector_depth_ratio_tolerance = depth_ratio_tolerance; + m_connector_size = 2.f * radius; + m_connector_size_tolerance = radius_tolerance; + m_connector_type = type; + m_connector_style = size_t(style); + m_connector_shape_id = size_t(shape); + } +} + +void GLGizmoAdvancedCut::render_connectors_input_window(float x, float y, float bottom_limit) +{ + CutConnectors &connectors = m_c->selection_info()->model_object()->cut_connectors; + + // update when change input window + m_imgui->set_requires_extra_frame(); + + ImGui::AlignTextToFramePadding(); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Connectors")); + + m_imgui->disabled_begin(connectors.empty()); + ImGui::SameLine(m_label_width); + if (render_reset_button("connectors", _u8L("Remove connectors"))) + reset_connectors(); + m_imgui->disabled_end(); + + m_imgui->text(_L("Type")); + ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.00f, 0.00f, 0.00f, 1.00f)); + bool type_changed = render_connect_type_radio_button(CutConnectorType::Plug); + type_changed |= render_connect_type_radio_button(CutConnectorType::Dowel); + if (type_changed) + apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.type = CutConnectorType(m_connector_type); }); + ImGui::PopStyleColor(1); + + std::vector connector_styles = {_u8L("Prizm"), _u8L("Frustum")}; + std::vector connector_shapes = { _u8L("Triangle"), _u8L("Square"), _u8L("Hexagon"), _u8L("Circle") }; + + m_imgui->disabled_begin(m_connector_type == CutConnectorType::Dowel); + if (type_changed && m_connector_type == CutConnectorType::Dowel) { + m_connector_style = size_t(CutConnectorStyle::Prizm); + apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); }); + } + + ImGuiWrapper::push_combo_style(m_parent.get_scale()); + if (render_combo(_u8L("Style"), connector_styles, m_connector_style)) + apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); }); + m_imgui->disabled_end(); + + if (render_combo(_u8L("Shape"), connector_shapes, m_connector_shape_id)) + apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); }); + ImGuiWrapper::pop_combo_style(); + + if (render_slider_double_input(_u8L("Depth ratio"), m_connector_depth_ratio, m_connector_depth_ratio_tolerance)) + apply_selected_connectors([this, &connectors](size_t idx) { + if (m_connector_depth_ratio > 0) + connectors[idx].height = m_connector_depth_ratio; + if (m_connector_depth_ratio_tolerance >= 0) + connectors[idx].height_tolerance = m_connector_depth_ratio_tolerance; + }); + + if (render_slider_double_input(_u8L("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; + if (m_connector_size_tolerance >= 0) + connectors[idx].radius_tolerance = m_connector_size_tolerance; + }); + + ImGui::Separator(); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); + float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; + show_tooltip_information(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)); + + ImGui::SameLine(); + if (m_imgui->button(_L("Confirm connectors"))) { + unselect_all_connectors(); + set_connectors_editing(false); + } + + ImGui::SameLine(2.75f * m_label_width); + + if (m_imgui->button(_L("Cancel"))) { + reset_connectors(); + set_connectors_editing(false); + } + + ImGui::PopStyleVar(2); +} + +void GLGizmoAdvancedCut::render_input_window_warning() const +{ + if (m_has_invalid_connector) { + wxString out = /*wxString(ImGui::WarningMarkerSmall)*/ "Warning: " + _L("Invalid connectors detected") + ":"; + if (m_info_stats.outside_cut_contour > size_t(0)) + out += "\n - " + std::to_string(m_info_stats.outside_cut_contour) + + (m_info_stats.outside_cut_contour == 1 ? L("connector is out of cut contour") : L("connectors are out of cut contour")); + if (m_info_stats.outside_bb > size_t(0)) + out += "\n - " + std::to_string(m_info_stats.outside_bb) + + (m_info_stats.outside_bb == 1 ? L("connector is out of object") : L("connectors is out of object")); + if (m_info_stats.is_overlap) + out += "\n - " + _L("Some connectors are overlapped"); + m_imgui->text(out); + } + if (!m_keep_upper && !m_keep_lower) m_imgui->text(/*wxString(ImGui::WarningMarkerSmall)*/"Warning: " + _L("Invalid state. \nNo one part is selected for keep after cut")); +} + +bool GLGizmoAdvancedCut::render_reset_button(const std::string &label_id, const std::string &tooltip) const +{ + const ImGuiStyle &style = ImGui::GetStyle(); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {1, style.ItemSpacing.y}); + + ImGui::PushStyleColor(ImGuiCol_Button, {0.25f, 0.25f, 0.25f, 0.0f}); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, {0.4f, 0.4f, 0.4f, 1.0f}); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, {0.4f, 0.4f, 0.4f, 1.0f}); + + bool revert = m_imgui->button(wxString(ImGui::RevertBtn)); + + ImGui::PopStyleColor(3); + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(tooltip.c_str(), ImGui::GetFontSize() * 20.0f); + + ImGui::PopStyleVar(); + + return revert; +} + +bool GLGizmoAdvancedCut::render_connect_type_radio_button(CutConnectorType type) +{ + ImGui::SameLine(type == CutConnectorType::Plug ? m_label_width : 2 * m_label_width); + ImGui::PushItemWidth(m_control_width); + + wxString radio_name; + switch (type) { + case CutConnectorType::Plug: + radio_name = L("Plug"); + break; + case CutConnectorType::Dowel: + radio_name = L("Dowel"); + break; + default: + break; + } + + if (m_imgui->radio_button(radio_name, m_connector_type == type)) { + m_connector_type = type; + update_connector_shape(); + return true; + } + return false; +} + +bool GLGizmoAdvancedCut::render_combo(const std::string &label, const std::vector &lines, size_t &selection_idx) +{ + ImGui::AlignTextToFramePadding(); + m_imgui->text(label); + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(m_control_width); + + size_t selection_out = selection_idx; + // It is necessary to use BeginGroup(). Otherwise, when using SameLine() is called, then other items will be drawn inside the combobox. + ImGui::BeginGroup(); + ImVec2 combo_pos = ImGui::GetCursorScreenPos(); + if (ImGui::BeginCombo(("##" + label).c_str(), "")) { + for (size_t line_idx = 0; line_idx < lines.size(); ++line_idx) { + ImGui::PushID(int(line_idx)); + if (ImGui::Selectable("", line_idx == selection_idx)) selection_out = line_idx; + + ImGui::SameLine(); + ImGui::Text("%s", lines[line_idx].c_str()); + ImGui::PopID(); + } + + ImGui::EndCombo(); + } + + ImVec2 backup_pos = ImGui::GetCursorScreenPos(); + ImGuiStyle &style = ImGui::GetStyle(); + + ImGui::SetCursorScreenPos(ImVec2(combo_pos.x + style.FramePadding.x, combo_pos.y + style.FramePadding.y)); + std::string str_line = selection_out < lines.size() ? lines[selection_out] : " "; + ImGui::Text("%s", str_line.c_str()); + ImGui::SetCursorScreenPos(backup_pos); + ImGui::EndGroup(); + + bool is_changed = selection_idx != selection_out; + selection_idx = selection_out; + + if (is_changed) update_connector_shape(); + + return is_changed; +} + +bool GLGizmoAdvancedCut::render_slider_double_input(const std::string &label, float &value_in, float &tolerance_in) +{ + ImGui::AlignTextToFramePadding(); + m_imgui->text(label); + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(m_control_width * 0.85f); + + bool m_imperial_units = false; + + float value = value_in; + if (m_imperial_units) value *= float(units_mm_to_in); + float old_val = value; + + constexpr float UndefMinVal = -0.1f; + + const BoundingBoxf3 bbox = bounding_box(); + float mean_size = float((bbox.size().x() + bbox.size().y() + bbox.size().z()) / 9.0); + float min_size = value_in < 0.f ? UndefMinVal : 2.f; + if (m_imperial_units) { + mean_size *= float(units_mm_to_in); + min_size *= float(units_mm_to_in); + } + std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _u8L("in") : "%.2f " + _u8L("mm"); + + m_imgui->slider_float(("##" + label).c_str(), &value, min_size, mean_size, format.c_str()); + value_in = value * float(m_imperial_units ? units_in_to_mm : 1.0); + + ImGui::SameLine(m_label_width + m_control_width + 3); + ImGui::PushItemWidth(m_control_width * 0.3f); + + float old_tolerance, tolerance = old_tolerance = tolerance_in * 100.f; + std::string format_t = tolerance_in < 0.f ? " " : "%.f %%"; + float min_tolerance = tolerance_in < 0.f ? UndefMinVal : 0.f; + + m_imgui->slider_float(("##tolerance_" + label).c_str(), &tolerance, min_tolerance, 20.f, format_t.c_str(), 1.f, true, _L("Tolerance")); + tolerance_in = tolerance * 0.01f; + + return !is_approx(old_val, value) || !is_approx(old_tolerance, tolerance); +} + +bool GLGizmoAdvancedCut::cut_line_processing() const +{ + return m_cut_line_begin != Vec3d::Zero(); +} + +void GLGizmoAdvancedCut::discard_cut_line_processing() +{ + m_cut_line_begin = m_cut_line_end = Vec3d::Zero(); +} + +bool GLGizmoAdvancedCut::process_cut_line(SLAGizmoEventType action, const Vec2d &mouse_position) +{ + const Camera &camera = wxGetApp().plater()->get_camera(); + + Vec3d pt; + Vec3d dir; + MeshRaycaster::line_from_mouse_pos_static(mouse_position, Transform3d::Identity(), camera, pt, dir); + dir.normalize(); + pt += dir; // Move the pt along dir so it is not clipped. + + if (action == SLAGizmoEventType::LeftDown && !cut_line_processing()) { + m_cut_line_begin = pt; + m_cut_line_end = pt; + return true; + } + + if (cut_line_processing()) { + m_cut_line_end = pt; + if (action == SLAGizmoEventType::LeftDown || action == SLAGizmoEventType::LeftUp) { + Vec3d line_dir = m_cut_line_end - m_cut_line_begin; + if (line_dir.norm() < 3.0) + return true; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Cut by line"); + + Vec3d cross_dir = line_dir.cross(dir).normalized(); + Eigen::Quaterniond q; + Transform3d m = Transform3d::Identity(); + m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(m_cut_plane_normal, cross_dir).toRotationMatrix(); + + m_rotate_matrix = m; + + const ModelObject * mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[m_c->selection_info()->get_active_instance()]; + Vec3d plane_center = get_plane_center(); + + auto update_plane_after_line_cut = [this](const Vec3d &deta_plane_center, const Transform3d& rotate_matrix) { + Vec3d plane_center = get_plane_center(); + + std::array plane_points_rot; + for (int i = 0; i < plane_points_rot.size(); i++) { + plane_points_rot[i] = m_cut_plane_points[i] - plane_center; + } + + for (uint32_t i = 0; i < plane_points_rot.size(); ++i) { + plane_points_rot[i] = rotate_matrix * plane_points_rot[i]; + } + + for (int i = 0; i < plane_points_rot.size(); i++) { + m_cut_plane_points[i] = plane_points_rot[i] + plane_center + deta_plane_center; + } + }; + + update_plane_after_line_cut(cross_dir * (cross_dir.dot(pt - m_cut_plane_center)), m_rotate_matrix); + + discard_cut_line_processing(); + } else if (action == SLAGizmoEventType::Dragging) + this->set_dirty(); + return true; + } + return false; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp index a040c8acc..45fe4419a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp @@ -3,9 +3,15 @@ #include "GLGizmoBase.hpp" #include "GLGizmoRotate.hpp" +#include "libslic3r/Model.hpp" namespace Slic3r { +enum class CutConnectorType : int; +class ModelVolume; +struct CutConnectorAttributes; + namespace GUI { +enum class SLAGizmoEventType : unsigned char; class GLGizmoAdvancedCut : public GLGizmoRotate3D { @@ -42,8 +48,11 @@ private: bool m_keep_upper; bool m_keep_lower; - bool m_cut_to_parts; - bool m_rotate_lower; + bool m_place_on_cut_upper{true}; + bool m_place_on_cut_lower{false}; + bool m_rotate_upper{false}; + bool m_rotate_lower{false}; + bool m_do_segment; double m_segment_smoothing_alpha; int m_segment_number; @@ -54,25 +63,93 @@ private: unsigned int m_last_active_id; + bool m_connectors_editing{false}; + bool m_show_shortcuts{false}; + + std::vector> m_shortcuts; + double m_label_width{150.0}; + double m_control_width{ 200.0 }; + + CutConnectorType m_connector_type; + size_t m_connector_style; + size_t m_connector_shape_id; + + float m_connector_depth_ratio{3.f}; + float m_connector_depth_ratio_tolerance{0.1f}; + + float m_connector_size{2.5f}; + float m_connector_size_tolerance{0.f}; + + TriangleMesh m_connector_mesh; + bool m_has_invalid_connector{false}; + + // remember the connectors which is selected + mutable std::vector m_selected; + int m_selected_count{0}; + + Vec3d m_cut_plane_center{Vec3d::Zero()}; + Vec3d m_cut_plane_normal{Vec3d::UnitZ()}; + + Vec3d m_cut_line_begin{Vec3d::Zero()}; + Vec3d m_cut_line_end{Vec3d::Zero()}; + + Transform3d m_rotate_matrix{Transform3d::Identity()}; + + std::map m_shapes; + + struct InvalidConnectorsStatistics + { + unsigned int outside_cut_contour; + unsigned int outside_bb; + bool is_overlap; + + void invalidate() + { + outside_cut_contour = 0; + outside_bb = 0; + is_overlap = false; + } + } m_info_stats; + + //GLSelectionRectangle m_selection_rectangle; + public: GLGizmoAdvancedCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); + bool gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_position, bool shift_down, bool alt_down, bool control_down); + bool on_key(wxKeyEvent &evt); + double get_movement() const { return m_movement; } void set_movement(double movement) const; void finish_rotation(); std::string get_tooltip() const override; + BoundingBoxf3 bounding_box() const; + //BoundingBoxf3 transformed_bounding_box(const Vec3d &plane_center, bool revert_move = false) const; + + bool is_looking_forward() const; + + bool unproject_on_cut_plane(const Vec2d &mouse_pos, Vec3d &pos, Vec3d &pos_world); + + virtual bool apply_clipping_plane() { return m_connectors_editing; } + protected: virtual bool on_init(); + virtual void on_load(cereal::BinaryInputArchive &ar) override; + virtual void on_save(cereal::BinaryOutputArchive &ar) const override; virtual std::string on_get_name() const; virtual void on_set_state(); virtual bool on_is_activable() const; - virtual void on_start_dragging(); + virtual CommonGizmosDataID on_get_requirements() const override; + virtual void on_start_dragging() override; + virtual void on_stop_dragging() override; virtual void on_update(const UpdateData& data); virtual void on_render(); virtual void on_render_for_picking(); virtual void on_render_input_window(float x, float y, float bottom_limit); + void show_tooltip_information(float x, float y); + virtual void on_enable_grabber(unsigned int id) { if (id < 3) @@ -97,6 +174,12 @@ protected: private: void perform_cut(const Selection& selection); + bool can_perform_cut() const; + void apply_connectors_in_model(ModelObject *mo, bool &create_dowels_as_separate_object); + + bool is_selection_changed(bool alt_down, bool shift_down); + void select_connector(int idx, bool select); + double calc_projection(const Linef3& mouse_ray) const; Vec3d calc_plane_normal(const std::array& plane_points) const; Vec3d calc_plane_center(const std::array& plane_points) const; @@ -107,6 +190,46 @@ private: std::array get_plane_points_world_coord() const; void reset_cut_plane(); void reset_all(); + + // update the connectors position so that the connectors are on the cut plane + void put_connectors_on_cut_plane(const Vec3d &cp_normal, double cp_offset); + void update_clipper(); + // on render + void render_cut_plane_and_grabbers(); + void render_connectors(); + void render_clipper_cut(); + void render_cut_line(); + void render_connector_model(GLModel &model, const std::array& color, Transform3d view_model_matrix); + + void clear_selection(); + void init_connector_shapes(); + void set_connectors_editing(bool connectors_editing); + void reset_connectors(); + void update_connector_shape(); + void apply_selected_connectors(std::function apply_fn); + void select_all_connectors(); + void unselect_all_connectors(); + void validate_connector_settings(); + bool add_connector(CutConnectors &connectors, const Vec2d &mouse_position); + bool delete_selected_connectors(); + bool is_outside_of_cut_contour(size_t idx, const CutConnectors &connectors, const Vec3d cur_pos); + bool is_conflict_for_connector(size_t idx, const CutConnectors &connectors, const Vec3d cur_pos); + void check_conflict_for_all_connectors(); + + // render input window + void render_cut_plane_input_window(float x, float y, float bottom_limit); + void init_connectors_input_window_data(); + void render_connectors_input_window(float x, float y, float bottom_limit); + void render_input_window_warning() const; + bool render_reset_button(const std::string &label_id, const std::string &tooltip) const; + bool render_connect_type_radio_button(CutConnectorType type); + + bool render_combo(const std::string &label, const std::vector &lines, size_t &selection_idx); + bool render_slider_double_input(const std::string &label, float &value_in, float &tolerance_in); + + bool cut_line_processing() const; + void discard_cut_line_processing(); + bool process_cut_line(SLAGizmoEventType action, const Vec2d &mouse_position); }; } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index 6d4359d43..ba8fd5280 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -162,6 +162,8 @@ public: virtual std::string get_action_snapshot_name() { return "Gizmo action"; } void set_common_data_pool(CommonGizmosDataPool* ptr) { m_c = ptr; } + virtual bool apply_clipping_plane() { return true; } + unsigned int get_sprite_id() const { return m_sprite_id; } int get_hover_id() const { return m_hover_id; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 3bced6a35..39bc98d7f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -336,8 +336,8 @@ bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_pos if (action == SLAGizmoEventType::LeftUp) { if (m_wait_for_up_event) { m_wait_for_up_event = false; - return true; } + return true; } // dragging the selection rectangle: diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 7fcb4efb7..65479b064 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -83,7 +83,7 @@ bool GLGizmoRotate::on_init() void GLGizmoRotate::on_start_dragging() { const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); - m_center = box.center(); + m_center = m_custom_center == Vec3d::Zero() ? box.center() : m_custom_center; m_radius = Offset + box.radius(); m_snap_coarse_in_radius = m_radius / 3.0f; m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; @@ -135,7 +135,7 @@ void GLGizmoRotate::on_render() const BoundingBoxf3& box = selection.get_bounding_box(); if (m_hover_id != 0 && !m_grabbers[0].dragging) { - m_center = box.center(); + m_center = m_custom_center == Vec3d::Zero() ? box.center() : m_custom_center; m_radius = Offset + box.radius(); m_snap_coarse_in_radius = m_radius / 3.0f; m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 25b8eaf53..9862ec290 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -34,6 +34,7 @@ private: Axis m_axis; double m_angle; + mutable Vec3d m_custom_center{Vec3d::Zero()}; mutable Vec3d m_center; mutable float m_radius; @@ -52,6 +53,8 @@ public: std::string get_tooltip() const override; + void set_center(const Vec3d &point) { m_custom_center = point; } + protected: bool on_init() override; std::string on_get_name() const override { return ""; } @@ -101,6 +104,13 @@ public: return tooltip; } + void set_center(const Vec3d &point) + { + m_gizmos[X].set_center(point); + m_gizmos[Y].set_center(point); + m_gizmos[Z].set_center(point); + } + protected: bool on_init() override; std::string on_get_name() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 6f61f7eba..0f98c5dd3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -63,6 +63,12 @@ std::string GLGizmoScale3D::get_tooltip() const return ""; } +void GLGizmoScale3D::enable_ununiversal_scale(bool enable) +{ + for (unsigned int i = 0; i < 6; ++i) + m_grabbers[i].enabled = enable; +} + bool GLGizmoScale3D::on_init() { for (int i = 0; i < 10; ++i) @@ -228,9 +234,11 @@ void GLGizmoScale3D::on_render() // BBS: when select multiple objects, uniform scale can be deselected, display the connection(4,5) //if (single_instance || single_volume) { + + if (m_grabbers[4].enabled && m_grabbers[5].enabled) { glsafe(::glColor4fv(m_grabbers[4].color.data())); render_grabbers_connection(4, 5); - //} + } glsafe(::glColor4fv(m_grabbers[2].color.data())); render_grabbers_connection(6, 7); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index e1c72370f..839c7f682 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -55,6 +55,7 @@ public: std::string get_tooltip() const override; + void enable_ununiversal_scale(bool enable); protected: virtual bool on_init() override; virtual std::string on_get_name() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 7c95d087f..73ed826fc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -427,8 +427,8 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if (action == SLAGizmoEventType::LeftUp) { if (m_wait_for_up_event) { m_wait_for_up_event = false; - return true; } + return true; } // dragging the selection rectangle: diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index d52969ba5..524264cd3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -449,7 +449,26 @@ void ObjectClipper::set_position(double pos, bool keep_normal) get_pool()->get_canvas()->set_as_dirty(); } +void ObjectClipper::set_range_and_pos(const Vec3d &cpl_normal, double cpl_offset, double pos) +{ + m_clp.reset(new ClippingPlane(cpl_normal, cpl_offset)); + m_clp_ratio = pos; + get_pool()->get_canvas()->set_as_dirty(); +} +bool ObjectClipper::is_projection_inside_cut(const Vec3d &point) const +{ + return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [point](const auto &cl) { + return cl->is_projection_inside_cut(point); + }); +} + +bool ObjectClipper::has_valid_contour() const +{ + return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [](const auto &cl) { + return cl->has_valid_contour(); + }); +} void SupportsClipper::on_update() { diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index 91c1bde44..f4211dfb5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -266,6 +266,10 @@ public: ClippingPlane* get_clipping_plane() const { return m_clp.get(); } void render_cut() const; + void set_range_and_pos(const Vec3d &cpl_normal, double cpl_offset, double pos); + + bool is_projection_inside_cut(const Vec3d &point_in) const; + bool has_valid_contour() const; protected: void on_update() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 49f879933..7575d1dd7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -627,6 +627,8 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p return dynamic_cast(m_gizmos[MmuSegmentation].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == Text) return dynamic_cast(m_gizmos[Text].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); + else if (m_current == Cut) + return dynamic_cast(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else return false; } @@ -872,7 +874,8 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) m_tooltip.clear(); if (evt.LeftDown() && (!control_down || grabber_contains_mouse())) { - if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation || m_current == Text) + if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || + m_current == Seam || m_current == MmuSegmentation || m_current == Text || m_current == Cut) && gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, evt.ShiftDown(), evt.AltDown())) // the gizmo got the event and took some action, there is no need to do anything more processed = true; @@ -902,7 +905,8 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) // event was taken care of by the SlaSupports gizmo processed = true; } - else if (evt.RightDown() && !control_down && selected_object_idx != -1 && (m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation) + else if (evt.RightDown() && !control_down && selected_object_idx != -1 + && (m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation || m_current == Cut) && gizmo_event(SLAGizmoEventType::RightDown, mouse_pos)) { // event was taken care of by the FdmSupports / Seam / MMUPainting gizmo processed = true; @@ -911,7 +915,8 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation)) // don't allow dragging objects with the Sla gizmo on processed = true; - else if (evt.Dragging() && !control_down && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation) + else if (evt.Dragging() && !control_down + && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation || m_current == Cut) && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown())) { // the gizmo got the event and took some action, no need to do anything more here m_parent.set_as_dirty(); @@ -924,10 +929,12 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) else if (evt.RightIsDown()) gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), true); } - else if (evt.LeftUp() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation) && !m_parent.is_mouse_dragging()) { + else if (evt.LeftUp() + && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation || m_current == Cut) + && !m_parent.is_mouse_dragging() + && gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), control_down)) { // in case SLA/FDM gizmo is selected, we just pass the LeftUp event and stop processing - neither // object moving or selecting is suppressed in that case - gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), control_down); processed = true; } else if (evt.LeftUp() && m_current == Flatten && m_gizmos[m_current]->get_hover_id() != -1) { @@ -937,8 +944,9 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) //wxGetApp().obj_manipul()->set_dirty(); processed = true; } - else if (evt.RightUp() && (m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation) && !m_parent.is_mouse_dragging()) { - gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), control_down); + else if (evt.RightUp() && (m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation || m_current == Cut) + && !m_parent.is_mouse_dragging() + && gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), control_down)) { processed = true; } else if (evt.LeftUp()) { @@ -1111,6 +1119,13 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) const int keyCode = evt.GetKeyCode(); bool processed = false; + // todo: zhimin Each gizmo should handle key event in it own on_key() function + if (m_current == Cut) { + if (GLGizmoAdvancedCut *gizmo_cut = dynamic_cast(get_current())) { + return gizmo_cut->on_key(evt); + } + } + if (evt.GetEventType() == wxEVT_KEY_UP) { if (m_current == SlaSupports || m_current == Hollow) @@ -1479,6 +1494,11 @@ GLGizmoBase* GLGizmosManager::get_current() const return ((m_current == Undefined) || m_gizmos.empty()) ? nullptr : m_gizmos[m_current].get(); } +GLGizmoBase* GLGizmosManager::get_gizmo(GLGizmosManager::EType type) const +{ + return ((type == Undefined) || m_gizmos.empty()) ? nullptr : m_gizmos[type].get(); +} + GLGizmosManager::EType GLGizmosManager::get_gizmo_from_name(const std::string& gizmo_name) const { std::vector selectable_idxs = get_selectable_idxs(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index c943bc388..4d13c3d07 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -230,6 +230,7 @@ public: EType get_current_type() const { return m_current; } GLGizmoBase* get_current() const; + GLGizmoBase *get_gizmo(GLGizmosManager::EType type) const; EType get_gizmo_from_name(const std::string& gizmo_name) const; bool is_running() const; @@ -311,7 +312,7 @@ public: //BBS: GUI refactor: GLToolbar adjust float get_scaled_total_height() const; float get_scaled_total_width() const; - //GizmoObjectManipulation& get_object_manipulation() { return m_object_manipulation; } + GizmoObjectManipulation& get_object_manipulation() { return m_object_manipulation; } bool get_uniform_scaling() const { return m_object_manipulation.get_uniform_scaling();} private: diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 027a51915..a14b36c7b 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -84,6 +84,10 @@ static const std::map font_icons = { {ImGui::TextSearchIcon , "im_text_search" }, {ImGui::TextSearchCloseIcon , "im_text_search_close" }, + + {ImGui::ExpandBtn , "expand_btn" }, + {ImGui::CollapseBtn , "collapse_btn" }, + {ImGui::RevertBtn , "revert_btn" }, }; static const std::map font_icons_large = { {ImGui::CloseNotifButton , "notification_close" }, @@ -2002,6 +2006,37 @@ void ImGuiWrapper::pop_button_disable_style() { ImGui::PopStyleColor(3); } +void ImGuiWrapper::push_combo_style(const float scale) +{ + if (m_is_dark_mode) { + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 1.0f * scale); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f * scale); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BG_DARK); + ImGui::PushStyleColor(ImGuiCol_BorderActive, ImVec4(0.00f, 0.68f, 0.26f, 1.00f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.00f, 0.68f, 0.26f, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.00f, 0.68f, 0.26f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.00f, 0.68f, 0.26f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ScrollbarBg, ImGuiWrapper::COL_WINDOW_BG_DARK); + ImGui::PushStyleColor(ImGuiCol_Button, {1.00f, 1.00f, 1.00f, 0.0f}); + } else { + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 1.0f * scale); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f * scale); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BG); + ImGui::PushStyleColor(ImGuiCol_BorderActive, ImVec4(0.00f, 0.68f, 0.26f, 1.00f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.00f, 0.68f, 0.26f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.00f, 0.68f, 0.26f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.00f, 0.68f, 0.26f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ScrollbarBg, ImGuiWrapper::COL_WINDOW_BG); + ImGui::PushStyleColor(ImGuiCol_Button, {1.00f, 1.00f, 1.00f, 0.0f}); + } +} + +void ImGuiWrapper::pop_combo_style() +{ + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(7); +} + void ImGuiWrapper::init_font(bool compress) { destroy_font(); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 001f9bea4..ebc8a69bf 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -213,6 +213,8 @@ public: static void pop_cancel_button_style(); static void push_button_disable_style(); static void pop_button_disable_style(); + static void push_combo_style(const float scale); + static void pop_combo_style(); //BBS static int TOOLBAR_WINDOW_FLAGS; diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 440b0ec0d..6fd3a54b8 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -11,6 +11,7 @@ #include #include +#include "CameraUtils.hpp" namespace Slic3r { @@ -75,7 +76,24 @@ void MeshClipper::render_cut() m_vertex_array.render(); } +bool MeshClipper::is_projection_inside_cut(const Vec3d &point_in) const +{ + if (!m_result || m_result->cut_islands.empty()) + return false; + Vec3d point = m_result->trafo.inverse() * point_in; + Point pt_2d = Point::new_scale(Vec2d(point.x(), point.y())); + for (const CutIsland &isl : m_result->cut_islands) { + if (isl.expoly_bb.contains(pt_2d) && isl.expoly.contains(pt_2d)) + return true; + } + return false; +} + +bool MeshClipper::has_valid_contour() const +{ + return m_result && std::any_of(m_result->cut_islands.begin(), m_result->cut_islands.end(), [](const CutIsland &isl) { return !isl.expoly.empty(); }); +} void MeshClipper::recalculate_triangles() { @@ -104,6 +122,9 @@ void MeshClipper::recalculate_triangles() tr.rotate(q); tr = m_trafo.get_matrix() * tr; + m_result = ClipResult(); + m_result->trafo = tr; + if (m_limiting_plane != ClippingPlane::ClipsNothing()) { // Now remove whatever ended up below the limiting plane (e.g. sinking objects). @@ -157,6 +178,13 @@ void MeshClipper::recalculate_triangles() } } + for (const ExPolygon &exp : expolys) { + m_result->cut_islands.push_back(CutIsland()); + CutIsland &isl = m_result->cut_islands.back(); + isl.expoly = std::move(exp); + isl.expoly_bb = get_extents(exp); + } + m_triangles2d = triangulate_expolygons_2f(expolys, m_trafo.get_matrix().matrix().determinant() < 0.); tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting @@ -180,6 +208,14 @@ Vec3f MeshRaycaster::get_triangle_normal(size_t facet_idx) const return m_normals[facet_idx]; } +void MeshRaycaster::line_from_mouse_pos_static(const Vec2d &mouse_pos, const Transform3d &trafo, const Camera &camera, Vec3d &point, Vec3d &direction) +{ + CameraUtils::ray_from_screen_pos(camera, mouse_pos, point, direction); + Transform3d inv = trafo.inverse(); + point = inv * point; + direction = inv.linear() * direction; +} + void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, Vec3d& point, Vec3d& direction) const { diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index bb8a1aa61..02c597da3 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -94,6 +94,9 @@ public: // be set in world coords. void render_cut(); + bool is_projection_inside_cut(const Vec3d &point) const; + bool has_valid_contour() const; + private: void recalculate_triangles(); @@ -105,6 +108,18 @@ private: std::vector m_triangles2d; GLIndexedVertexArray m_vertex_array; bool m_triangles_valid = false; + + struct CutIsland + { + ExPolygon expoly; + BoundingBox expoly_bb; + }; + struct ClipResult + { + std::vector cut_islands; + Transform3d trafo; // this rotates the cut into world coords + }; + std::optional m_result; // the cut plane }; @@ -121,6 +136,9 @@ public: { } + static void line_from_mouse_pos_static(const Vec2d &mouse_pos, const Transform3d &trafo, + const Camera &camera, Vec3d &point, Vec3d &direction); + void line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, Vec3d& point, Vec3d& direction) const; diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index dd60d6fa6..51ef98f67 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -136,7 +136,7 @@ void MsgDialog::SetButtonLabel(wxWindowID btn_id, const wxString& label, bool se Button* MsgDialog::add_button(wxWindowID btn_id, bool set_focus /*= false*/, const wxString& label/* = wxString()*/) { - Button* btn = new Button(this, label); + Button* btn = new Button(this, label, "", 0, 0, btn_id); ButtonSizeType type; if (label.length() < 5) { diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 7513d3397..77d1dc08e 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -1020,6 +1020,7 @@ void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType ty case InfoItemType::MmuSegmentation: text += format(_L_PLURAL("%1$d Object has color painting.", "%1$d Objects have color painting.",(*it).second), (*it).second) + "\n"; break; // BBS //case InfoItemType::Sinking: text += format(("%1$d Object has partial sinking.", "%1$d Objects have partial sinking.", (*it).second), (*it).second) + "\n"; break; + case InfoItemType::CutConnectors: text += format(_L_PLURAL("%1$d object was loaded as a part of cut object.", "%1$d objects were loaded as parts of cut object", (*it).second), (*it).second) + "\n"; break; default: BOOST_LOG_TRIVIAL(error) << "Unknown InfoItemType: " << (*it).second; break; } } diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 6895b50e0..76d9c616b 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -37,6 +37,7 @@ static constexpr char LayerRootIcon[] = "blank"; static constexpr char LayerIcon[] = "blank"; static constexpr char WarningIcon[] = "obj_warning"; static constexpr char WarningManifoldIcon[] = "obj_warning"; +static constexpr char LockIcon[] = "cut_"; ObjectDataViewModelNode::ObjectDataViewModelNode(PartPlate* part_plate, wxString name) : m_parent(nullptr), @@ -65,6 +66,7 @@ const std::map INFO_ITEMS{ //{ InfoItemType::CustomSeam, {L("Paint-on seam"), "seam_" }, }, { InfoItemType::MmuSegmentation, {L("Color painting"), "mmu_segmentation"}, }, //{ InfoItemType::Sinking, {L("Sinking"), "objlist_sinking"}, }, + { InfoItemType::CutConnectors, {L("Cut connectors"), "cut_connectors" }, }, }; ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, @@ -411,6 +413,7 @@ ObjectDataViewModel::ObjectDataViewModel() m_volume_bmps = MenuFactory::get_volume_bitmaps(); m_warning_bmp = create_scaled_bitmap(WarningIcon); m_warning_manifold_bmp = create_scaled_bitmap(WarningManifoldIcon); + m_lock_bmp = create_scaled_bitmap(LockIcon); for (auto item : INFO_ITEMS) m_info_bmps[item.first] = create_scaled_bitmap(item.second.bmp_name); @@ -490,7 +493,46 @@ wxDataViewItem ObjectDataViewModel::AddOutsidePlate(bool refresh) return plate_item; } -wxDataViewItem ObjectDataViewModel::AddObject(ModelObject* model_object, std::string warning_bitmap, bool refresh) +void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode *node) +{ + int vol_type = static_cast(node->GetVolumeType()); + bool is_volume_node = vol_type >= 0; + + if (!node->has_warning_icon() && !node->has_lock()) { + node->SetBitmap(is_volume_node ? m_volume_bmps.at(vol_type) : m_empty_bmp); + return; + } + + std::string scaled_bitmap_name = std::string(); + if (node->has_warning_icon()) + scaled_bitmap_name += node->warning_icon_name(); + if (node->has_lock()) + scaled_bitmap_name += LockIcon; + if (is_volume_node) + scaled_bitmap_name += std::to_string(vol_type); + + wxBitmap *bmp = m_bitmap_cache->find(scaled_bitmap_name); + if (!bmp) { + std::vector bmps; + if (node->has_warning_icon()) + bmps.emplace_back(node->warning_icon_name() == WarningIcon ? m_warning_bmp : m_warning_manifold_bmp); + if (node->has_lock()) + bmps.emplace_back(m_lock_bmp); + if (is_volume_node) + bmps.emplace_back(m_volume_bmps[vol_type]); + bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); + } + + node->SetBitmap(*bmp); +} + +void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode *node, bool has_lock) +{ + node->SetLock(has_lock); + UpdateBitmapForNode(node); +} + +wxDataViewItem ObjectDataViewModel::AddObject(ModelObject *model_object, std::string warning_bitmap, bool has_lock, bool refresh) { // get object node params wxString name = from_u8(model_object->name); @@ -512,6 +554,7 @@ wxDataViewItem ObjectDataViewModel::AddObject(ModelObject* model_object, std::st const wxString extruder_str = wxString::Format("%d", extruder); auto obj_node = new ObjectDataViewModelNode(name, extruder_str, plate_idx, model_object); obj_node->SetWarningBitmap(GetWarningBitmap(warning_bitmap), warning_bitmap); + UpdateBitmapForNode(obj_node, has_lock); if (plate_node != nullptr) { obj_node->m_parent = plate_node; @@ -2138,6 +2181,7 @@ void ObjectDataViewModel::Rescale() m_volume_bmps = MenuFactory::get_volume_bitmaps(); m_warning_bmp = create_scaled_bitmap(WarningIcon); m_warning_manifold_bmp = create_scaled_bitmap(WarningManifoldIcon); + m_lock_bmp = create_scaled_bitmap(LockIcon); for (auto item : INFO_ITEMS) m_info_bmps[item.first] = create_scaled_bitmap(item.second.bmp_name); @@ -2256,6 +2300,26 @@ void ObjectDataViewModel::UpdateWarningIcon(const wxDataViewItem& item, const st AddWarningIcon(item, warning_icon_name); } +void ObjectDataViewModel::UpdateCutObjectIcon(const wxDataViewItem &item, bool has_lock) +{ + if (!item.IsOk()) + return; + ObjectDataViewModelNode* node = static_cast(item.GetID()); + if (node->has_lock() == has_lock) + return; + + node->SetLock(has_lock); + UpdateBitmapForNode(node); + + if (node->GetType() & itObject) { + wxDataViewItemArray children; + GetChildren(item, children); + for (const wxDataViewItem &child : children) + UpdateCutObjectIcon(child, has_lock); + } + ItemChanged(item); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index ec9816227..9aa40f510 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -61,6 +61,7 @@ enum class InfoItemType //CustomSeam, MmuSegmentation, //Sinking + CutConnectors, }; class ObjectDataViewModelNode; @@ -92,6 +93,7 @@ class ObjectDataViewModelNode PrintIndicator m_printable {piUndef}; wxBitmap m_printable_icon; std::string m_warning_icon_name{ "" }; + bool m_has_lock{false}; // for cut object icon std::string m_action_icon_name = ""; ModelVolumeType m_volume_type; @@ -224,6 +226,7 @@ public: void SetBitmap(const wxBitmap &icon) { m_bmp = icon; } void SetExtruder(const wxString &extruder) { m_extruder = extruder; } void SetWarningBitmap(const wxBitmap& icon, const std::string& warning_icon_name) { m_bmp = icon; m_warning_icon_name = warning_icon_name; } + void SetLock(bool has_lock) { m_has_lock = has_lock; } const wxBitmap& GetBitmap() const { return m_bmp; } const wxString& GetName() const { return m_name; } ItemType GetType() const { return m_type; } @@ -297,6 +300,8 @@ public: #endif /* NDEBUG */ bool invalid() const { return m_idx < -1; } bool has_warning_icon() const { return !m_warning_icon_name.empty(); } + std::string warning_icon_name() const { return m_warning_icon_name; } + bool has_lock() const { return m_has_lock; } private: friend class ObjectDataViewModel; @@ -319,6 +324,7 @@ class ObjectDataViewModel :public wxDataViewModel wxBitmap m_empty_bmp; wxBitmap m_warning_bmp; wxBitmap m_warning_manifold_bmp; + wxBitmap m_lock_bmp; ObjectDataViewModelNode* m_plate_outside; @@ -330,7 +336,7 @@ public: void Init(); wxDataViewItem AddPlate(PartPlate* part_plate, wxString name = wxEmptyString, bool refresh = true); - wxDataViewItem AddObject(ModelObject* model_object, std::string warning_bitmap, bool refresh = true); + wxDataViewItem AddObject(ModelObject* model_object, std::string warning_bitmap, bool has_lock = false, bool refresh = true); wxDataViewItem AddVolumeChild( const wxDataViewItem &parent_item, const wxString &name, const Slic3r::ModelVolumeType volume_type, @@ -462,6 +468,7 @@ public: void AddWarningIcon(const wxDataViewItem& item, const std::string& warning_name); void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false); void UpdateWarningIcon(const wxDataViewItem& item, const std::string& warning_name); + void UpdateCutObjectIcon(const wxDataViewItem &item, bool has_cut_icon); bool HasWarningIcon(const wxDataViewItem& item) const; t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const; @@ -482,6 +489,9 @@ private: wxBitmap& GetWarningBitmap(const std::string& warning_icon_name); void ReparentObject(ObjectDataViewModelNode* plate, ObjectDataViewModelNode* object); wxDataViewItem AddOutsidePlate(bool refresh = true); + + void UpdateBitmapForNode(ObjectDataViewModelNode *node); + void UpdateBitmapForNode(ObjectDataViewModelNode *node, bool has_lock); }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 403edc4bf..d42e3d8d2 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1922,7 +1922,7 @@ struct Plater::priv void select_all(); void deselect_all(); void remove(size_t obj_idx); - void delete_object_from_model(size_t obj_idx, bool refresh_immediately = true); //BBS + bool delete_object_from_model(size_t obj_idx, bool refresh_immediately = true); //BBS void delete_all_objects_from_model(); void reset(bool apply_presets_change = false); void center_selection(); @@ -4029,13 +4029,31 @@ void Plater::priv::remove(size_t obj_idx) } -void Plater::priv::delete_object_from_model(size_t obj_idx, bool refresh_immediately) +bool Plater::priv::delete_object_from_model(size_t obj_idx, bool refresh_immediately) { + // check if object isn't cut + // show warning message that "cut consistancy" will not be supported any more + ModelObject *obj = model.objects[obj_idx]; + if (obj->is_cut()) { + InfoDialog dialog(q, _L("Delete object which is a part of cut object"), + _L("You try to delete an object which is a part of a cut object.\n" + "This action will break a cut correspondence.\n" + "After that model consistency can't be guaranteed."), + false, wxYES | wxCANCEL | wxCANCEL_DEFAULT | wxICON_WARNING); + dialog.SetButtonLabel(wxID_YES, _L("Delete")); + if (dialog.ShowModal() == wxID_CANCEL) + return false; + } + std::string snapshot_label = "Delete Object"; - if (! model.objects[obj_idx]->name.empty()) - snapshot_label += ": " + model.objects[obj_idx]->name; + if (!obj->name.empty()) + snapshot_label += ": " + obj->name; Plater::TakeSnapshot snapshot(q, snapshot_label); m_ui_jobs.cancel_all(); + + if (obj->is_cut()) + sidebar->obj_list()->invalidate_cut_info_for_object(obj_idx); + model.delete_object(obj_idx); //BBS: notify partplate the instance removed partplate_list.notify_instance_removed(obj_idx, -1); @@ -4045,6 +4063,8 @@ void Plater::priv::delete_object_from_model(size_t obj_idx, bool refresh_immedia update(); object_list_changed(); } + + return true; } void Plater::priv::delete_all_objects_from_model() @@ -6825,22 +6845,29 @@ bool Plater::priv::has_assemble_view() const bool Plater::priv::can_scale_to_print_volume() const { const BuildVolume::Type type = this->bed.build_volume().type(); - return !view3D->get_canvas3d()->get_selection().is_empty() && (type == BuildVolume::Type::Rectangle || type == BuildVolume::Type::Circle); + return !sidebar->obj_list()->has_selected_cut_object() + && !view3D->get_canvas3d()->get_selection().is_empty() + && (type == BuildVolume::Type::Rectangle || type == BuildVolume::Type::Circle); } #endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT bool Plater::priv::can_mirror() const { - return get_selection().is_from_single_instance(); + return !sidebar->obj_list()->has_selected_cut_object() + && get_selection().is_from_single_instance(); } bool Plater::priv::can_replace_with_stl() const { - return get_selection().get_volume_idxs().size() == 1; + return !sidebar->obj_list()->has_selected_cut_object() + && get_selection().get_volume_idxs().size() == 1; } bool Plater::priv::can_reload_from_disk() const { + if (sidebar->obj_list()->has_selected_cut_object()) + return false; + #if ENABLE_RELOAD_FROM_DISK_REWORK // collect selected reloadable ModelVolumes std::vector> selected_volumes = reloadable_volumes(model, get_selection()); @@ -7049,7 +7076,8 @@ bool Plater::priv::can_increase_instances() const return false; int obj_idx = get_selected_object_idx(); - return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()); + return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) + && !sidebar->obj_list()->has_selected_cut_object(); } bool Plater::priv::can_decrease_instances() const @@ -7059,7 +7087,8 @@ bool Plater::priv::can_decrease_instances() const return false; int obj_idx = get_selected_object_idx(); - return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1); + return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1) + && !sidebar->obj_list()->has_selected_cut_object(); } bool Plater::priv::can_split_to_objects() const @@ -8871,7 +8900,7 @@ void Plater::trigger_restore_project(int skip_confirm) } //BBS -void Plater::delete_object_from_model(size_t obj_idx, bool refresh_immediately) { p->delete_object_from_model(obj_idx, refresh_immediately); } +bool Plater::delete_object_from_model(size_t obj_idx, bool refresh_immediately) { return p->delete_object_from_model(obj_idx, refresh_immediately); } //BBS: delete all from model void Plater::delete_all_objects_from_model() @@ -9083,8 +9112,6 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, std::array plane if (! attributes.has(ModelObjectCutAttribute::KeepUpper) && ! attributes.has(ModelObjectCutAttribute::KeepLower)) return; - Plater::TakeSnapshot snapshot(this, "Cut by Plane"); - wxBusyCursor wait; // BBS: replace z with plane_points const auto new_objects = object->cut(instance_idx, plane_points, attributes); @@ -9092,6 +9119,14 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, std::array plane remove(obj_idx); p->load_model_objects(new_objects); + // now process all updates of the 3d scene + update(); + + // Update InfoItems in ObjectList after update() to use of a correct value of the GLCanvas3D::is_sinking(), + // which is updated after a view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH) call + for (size_t idx = 0; idx < p->model.objects.size(); idx++) + wxGetApp().obj_list()->update_info_items(idx); + Selection& selection = p->get_selection(); size_t last_id = p->model.objects.size() - 1; for (size_t i = 0; i < new_objects.size(); ++i) diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index f6c7420d2..a132f3d38 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -293,7 +293,7 @@ public: int close_with_confirm(std::function second_check = nullptr); // BBS close project //BBS: trigger a restore project event void trigger_restore_project(int skip_confirm = 0); - void delete_object_from_model(size_t obj_idx, bool refresh_immediately = true); // BBS support refresh immediately + bool delete_object_from_model(size_t obj_idx, bool refresh_immediately = true); // BBS support refresh immediately void delete_all_objects_from_model(); //BBS delete all objects from model void set_selected_visible(bool visible); void remove_selected(); diff --git a/src/slic3r/GUI/Widgets/Button.cpp b/src/slic3r/GUI/Widgets/Button.cpp index 37709e977..9bd9fbb16 100644 --- a/src/slic3r/GUI/Widgets/Button.cpp +++ b/src/slic3r/GUI/Widgets/Button.cpp @@ -36,15 +36,15 @@ Button::Button() std::make_pair(*wxBLACK, (int) StateColor::Normal)); } -Button::Button(wxWindow* parent, wxString text, wxString icon, long style, int iconSize) +Button::Button(wxWindow* parent, wxString text, wxString icon, long style, int iconSize, wxWindowID btn_id) : Button() { - Create(parent, text, icon, style, iconSize); + Create(parent, text, icon, style, iconSize, btn_id); } -bool Button::Create(wxWindow* parent, wxString text, wxString icon, long style, int iconSize) +bool Button::Create(wxWindow* parent, wxString text, wxString icon, long style, int iconSize, wxWindowID btn_id) { - StaticBox::Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, style); + StaticBox::Create(parent, btn_id, wxDefaultPosition, wxDefaultSize, style); state_handler.attach({&text_color}); state_handler.update_binds(); //BBS set default font diff --git a/src/slic3r/GUI/Widgets/Button.hpp b/src/slic3r/GUI/Widgets/Button.hpp index 100b430e6..2f5c8eaea 100644 --- a/src/slic3r/GUI/Widgets/Button.hpp +++ b/src/slic3r/GUI/Widgets/Button.hpp @@ -24,9 +24,9 @@ class Button : public StaticBox public: Button(); - Button(wxWindow* parent, wxString text, wxString icon = "", long style = 0, int iconSize = 0); + Button(wxWindow* parent, wxString text, wxString icon = "", long style = 0, int iconSize = 0, wxWindowID btn_id = wxID_ANY); - bool Create(wxWindow* parent, wxString text, wxString icon = "", long style = 0, int iconSize = 0); + bool Create(wxWindow* parent, wxString text, wxString icon = "", long style = 0, int iconSize = 0, wxWindowID btn_id = wxID_ANY); void SetLabel(const wxString& label) override;