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, ">");
+ // 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;