Cut WIP: Code refactoring for ae21667786
+ ObjectList: Fixed list of the types for "Change type" dialog, when object is cut. + CutGizmo: * Warning line is extended for information about invalid connectors * Fixed a crash on undo/Redo, when cutGizmo is active
This commit is contained in:
parent
ae21667786
commit
18edc71254
7 changed files with 82 additions and 104 deletions
|
@ -4155,10 +4155,11 @@ void ObjectList::change_part_type()
|
|||
if (obj_idx < 0) return;
|
||||
|
||||
const ModelVolumeType type = volume->type();
|
||||
const ModelObject* obj = object(obj_idx);
|
||||
if (type == ModelVolumeType::MODEL_PART)
|
||||
{
|
||||
int model_part_cnt = 0;
|
||||
for (auto vol : (*m_objects)[obj_idx]->volumes) {
|
||||
for (auto vol : obj->volumes) {
|
||||
if (vol->type() == ModelVolumeType::MODEL_PART)
|
||||
++model_part_cnt;
|
||||
}
|
||||
|
@ -4169,9 +4170,18 @@ void ObjectList::change_part_type()
|
|||
}
|
||||
}
|
||||
|
||||
const wxString names[] = { _L("Part"), _L("Negative Volume"), _L("Modifier"), _L("Support Blocker"), _L("Support Enforcer") };
|
||||
auto new_type = ModelVolumeType(wxGetApp().GetSingleChoiceIndex(_L("Type:"), _L("Select type of part"), wxArrayString(5, names), int(type)));
|
||||
const bool is_cut_object = obj->is_cut();
|
||||
|
||||
wxArrayString names;
|
||||
names.Alloc(is_cut_object ? 3 : 5);
|
||||
if (!is_cut_object)
|
||||
for (const wxString& type : { _L("Part"), _L("Negative Volume") })
|
||||
names.Add(type);
|
||||
for (const wxString& type : { _L("Modifier"), _L("Support Blocker"), _L("Support Enforcer") } )
|
||||
names.Add(type);
|
||||
|
||||
const int type_shift = is_cut_object ? 2 : 0;
|
||||
auto new_type = ModelVolumeType(type_shift + wxGetApp().GetSingleChoiceIndex(_L("Type:"), _L("Select type of part"), names, int(type) - type_shift));
|
||||
if (new_type == type || new_type == ModelVolumeType::INVALID)
|
||||
return;
|
||||
|
||||
|
|
|
@ -928,6 +928,7 @@ void GLGizmoCut3D::on_set_state()
|
|||
{
|
||||
if (m_state == On) {
|
||||
update_bb();
|
||||
m_connectors_editing = !m_selected.empty();
|
||||
|
||||
// initiate archived values
|
||||
m_ar_plane_center = m_plane_center;
|
||||
|
@ -937,6 +938,7 @@ void GLGizmoCut3D::on_set_state()
|
|||
}
|
||||
else {
|
||||
m_c->object_clipper()->release();
|
||||
m_selected.clear();
|
||||
}
|
||||
force_update_clipper_on_render = m_state == On;
|
||||
}
|
||||
|
@ -1257,16 +1259,20 @@ void GLGizmoCut3D::on_stop_dragging()
|
|||
|
||||
void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool force/* = false*/)
|
||||
{
|
||||
const BoundingBoxf3 tbb = transformed_bounding_box(center_pos);
|
||||
|
||||
bool can_set_center_pos = force || (tbb.max.z() > -1. && tbb.min.z() < 1.);
|
||||
bool can_set_center_pos = force;
|
||||
if (!can_set_center_pos) {
|
||||
const double old_dist = (m_bb_center - m_plane_center).norm();
|
||||
const double new_dist = (m_bb_center - center_pos).norm();
|
||||
// check if forcing is reasonable
|
||||
if ( new_dist < old_dist)
|
||||
const BoundingBoxf3 tbb = transformed_bounding_box(center_pos);
|
||||
if (tbb.max.z() > -1. && tbb.min.z() < 1.)
|
||||
can_set_center_pos = true;
|
||||
else {
|
||||
const double old_dist = (m_bb_center - m_plane_center).norm();
|
||||
const double new_dist = (m_bb_center - center_pos).norm();
|
||||
// check if forcing is reasonable
|
||||
if (new_dist < old_dist)
|
||||
can_set_center_pos = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (can_set_center_pos) {
|
||||
m_plane_center = center_pos;
|
||||
m_center_offset = m_plane_center - m_bb_center;
|
||||
|
@ -1299,7 +1305,7 @@ BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(const Vec3d& plane_center,
|
|||
if (!mo)
|
||||
return ret;
|
||||
const int instance_idx = sel_info->get_active_instance();
|
||||
if (instance_idx < 0)
|
||||
if (instance_idx < 0 || mo->instances.empty())
|
||||
return ret;
|
||||
const ModelInstance* mi = mo->instances[instance_idx];
|
||||
|
||||
|
@ -1372,7 +1378,6 @@ bool GLGizmoCut3D::update_bb()
|
|||
clear_selection();
|
||||
if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info())
|
||||
m_selected.resize(selection->model_object()->cut_connectors.size(), false);
|
||||
m_connectors_editing = !m_selected.empty();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1791,8 +1796,18 @@ void GLGizmoCut3D::init_input_window_data(CutConnectors &connectors)
|
|||
|
||||
void GLGizmoCut3D::render_input_window_warning() const
|
||||
{
|
||||
if (wxGetApp().plater()->printer_technology() == ptFFF && m_has_invalid_connector)
|
||||
m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected."));
|
||||
if (wxGetApp().plater()->printer_technology() == ptFFF && m_has_invalid_connector) {
|
||||
wxString out = wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected") + ":";
|
||||
if (m_info_stats.outside_cut_contour > size_t(0))
|
||||
out += "\n - " + format_wxstr(_L_PLURAL("%1$d connector is out of cut contour", "%1$d connectors are out of cut contour", m_info_stats.outside_cut_contour),
|
||||
m_info_stats.outside_cut_contour);
|
||||
if (m_info_stats.outside_bb > size_t(0))
|
||||
out += "\n - " + format_wxstr(_L_PLURAL("%1$d connector is out of object", "%1$d connectors are out of object", m_info_stats.outside_bb),
|
||||
m_info_stats.outside_bb);
|
||||
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) + _L("Invalid state. \nNo one part is selected for keep after cut"));
|
||||
}
|
||||
|
@ -1848,24 +1863,30 @@ Transform3d GLGizmoCut3D::get_volume_transformation(const ModelVolume* volume) c
|
|||
bool GLGizmoCut3D::is_conflict_for_connector(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()->containes(cur_pos))
|
||||
if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(cur_pos)) {
|
||||
m_info_stats.outside_cut_contour++;
|
||||
return true;
|
||||
}
|
||||
|
||||
const CutConnector& cur_connector = connectors[idx];
|
||||
const Transform3d matrix = translation_transform(cur_pos) * m_rotation_m *
|
||||
scale_transform(Vec3f(cur_connector.radius, cur_connector.radius, cur_connector.height).cast<double>());
|
||||
const BoundingBoxf3 cur_tbb = m_shapes[cur_connector.attribs].model.get_bounding_box().transformed(matrix);
|
||||
|
||||
if (!bounding_box().contains(cur_tbb))
|
||||
if (!bounding_box().contains(cur_tbb)) {
|
||||
m_info_stats.outside_bb++;
|
||||
return true;
|
||||
}
|
||||
|
||||
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))
|
||||
if ((connector.pos - cur_connector.pos).norm() < double(connector.radius + cur_connector.radius)) {
|
||||
m_info_stats.is_overlap = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -1899,6 +1920,7 @@ void GLGizmoCut3D::render_connectors()
|
|||
const Vec3d& normal = cp && cp->is_active() ? cp->get_normal() : m_clp_normal;
|
||||
|
||||
m_has_invalid_connector = false;
|
||||
m_info_stats.invalidate();
|
||||
|
||||
for (size_t i = 0; i < connectors.size(); ++i) {
|
||||
const CutConnector& connector = connectors[i];
|
||||
|
@ -1970,6 +1992,8 @@ void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, bool &create_dowel
|
|||
|
||||
void GLGizmoCut3D::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();
|
||||
|
||||
|
@ -1985,13 +2009,13 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
|
|||
|
||||
// m_cut_z is the distance from the bed. Subtract possible SLA elevation.
|
||||
const double sla_shift_z = selection.get_first_volume()->get_sla_shift_z();
|
||||
const double object_cut_z = m_plane_center.z() - sla_shift_z;
|
||||
|
||||
const Vec3d instance_offset = mo->instances[instance_idx]->get_offset();
|
||||
Vec3d cut_center_offset = m_plane_center - instance_offset;
|
||||
cut_center_offset[Z] -= sla_shift_z;
|
||||
|
||||
if (0.0 < object_cut_z) {
|
||||
// perform cut
|
||||
{
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by Plane"));
|
||||
|
||||
bool create_dowels_as_separate_object = false;
|
||||
|
@ -2008,9 +2032,6 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
|
|||
only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) |
|
||||
only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels));
|
||||
}
|
||||
else {
|
||||
// the object is SLA-elevated and the plane is under it.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -74,6 +74,19 @@ class GLGizmoCut3D : public GLGizmoBase
|
|||
|
||||
Vec3d m_old_center;
|
||||
|
||||
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;
|
||||
|
||||
bool m_keep_upper{ true };
|
||||
bool m_keep_lower{ true };
|
||||
bool m_place_on_cut_upper{ true };
|
||||
|
|
|
@ -456,62 +456,14 @@ void ObjectClipper::render_cut() const
|
|||
}
|
||||
}
|
||||
|
||||
bool ObjectClipper::containes(Vec3d point) const
|
||||
bool ObjectClipper::is_projection_inside_cut(const Vec3d& point) const
|
||||
{
|
||||
if (m_clp_ratio == 0.)
|
||||
return false;
|
||||
const SelectionInfo* sel_info = get_pool()->selection_info();
|
||||
int sel_instance_idx = sel_info->get_active_instance();
|
||||
if (sel_instance_idx < 0)
|
||||
return false;
|
||||
const ModelObject* mo = sel_info->model_object();
|
||||
const Geometry::Transformation inst_trafo = mo->instances[sel_instance_idx]->get_transformation();
|
||||
|
||||
size_t clipper_id = 0;
|
||||
for (const ModelVolume* mv : mo->volumes) {
|
||||
const Geometry::Transformation vol_trafo = mv->get_transformation();
|
||||
Geometry::Transformation trafo = inst_trafo * vol_trafo;
|
||||
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
|
||||
|
||||
auto& clipper = m_clippers[clipper_id];
|
||||
clipper->set_plane(*m_clp);
|
||||
clipper->set_transformation(trafo);
|
||||
clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
|
||||
if (clipper->contains(point))
|
||||
return true;
|
||||
|
||||
++clipper_id;
|
||||
}
|
||||
return false;
|
||||
return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [point](const std::unique_ptr<MeshClipper>& cl) { return cl->is_projection_inside_cut(point); });
|
||||
}
|
||||
|
||||
bool ObjectClipper::has_valid_contour() const
|
||||
{
|
||||
if (m_clp_ratio == 0.)
|
||||
return false;
|
||||
const SelectionInfo* sel_info = get_pool()->selection_info();
|
||||
int sel_instance_idx = sel_info->get_active_instance();
|
||||
if (sel_instance_idx < 0)
|
||||
return false;
|
||||
const ModelObject* mo = sel_info->model_object();
|
||||
const Geometry::Transformation inst_trafo = mo->instances[sel_instance_idx]->get_transformation();
|
||||
|
||||
size_t clipper_id = 0;
|
||||
for (const ModelVolume* mv : mo->volumes) {
|
||||
const Geometry::Transformation vol_trafo = mv->get_transformation();
|
||||
Geometry::Transformation trafo = inst_trafo * vol_trafo;
|
||||
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
|
||||
|
||||
auto& clipper = m_clippers[clipper_id];
|
||||
clipper->set_plane(*m_clp);
|
||||
clipper->set_transformation(trafo);
|
||||
clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
|
||||
if (clipper->has_valid_contour())
|
||||
return true;
|
||||
|
||||
++clipper_id;
|
||||
}
|
||||
return false;
|
||||
return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [](const std::unique_ptr<MeshClipper>& cl) { return cl->has_valid_contour(); });
|
||||
}
|
||||
|
||||
void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal)
|
||||
|
|
|
@ -266,7 +266,7 @@ public:
|
|||
void pass_mouse_click(const Vec3d& pt);
|
||||
std::vector<Vec3d> get_disabled_contours() const;
|
||||
|
||||
bool containes(Vec3d point) const;
|
||||
bool is_projection_inside_cut(const Vec3d& point_in) const;
|
||||
bool has_valid_contour() const;
|
||||
|
||||
|
||||
|
|
|
@ -146,41 +146,23 @@ void MeshClipper::render_contour()
|
|||
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
|
||||
}
|
||||
|
||||
bool MeshClipper::contains(Vec3d point)
|
||||
bool MeshClipper::is_projection_inside_cut(const Vec3d& point_in) const
|
||||
{
|
||||
if (!m_result)
|
||||
recalculate_triangles();
|
||||
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 (CutIsland& isl : m_result->cut_islands) {
|
||||
BoundingBoxf3 bb = isl.model_expanded.get_bounding_box();
|
||||
|
||||
// instead of using of standard bb.contains(point)
|
||||
// because of precision (Note, that model_expanded is pretranslate(0.003 * normal.normalized()))
|
||||
constexpr double pres = 0.01;
|
||||
bool ret = (point.x() > bb.min.x() || is_approx(point.x(), bb.min.x(), pres)) && (point.x() < bb.max.x() || is_approx(point.x(), bb.max.x(), pres))
|
||||
&& (point.y() > bb.min.y() || is_approx(point.y(), bb.min.y(), pres)) && (point.y() < bb.max.y() || is_approx(point.y(), bb.max.y(), pres))
|
||||
&& (point.z() > bb.min.z() || is_approx(point.z(), bb.min.z(), pres)) && (point.z() < bb.max.z() || is_approx(point.z(), bb.max.z(), pres));
|
||||
if (ret) {
|
||||
// when we detected, that model_expanded's bb contains a point, then check if its polygon contains this point
|
||||
Vec3d point_inv = m_result->trafo.inverse() * point;
|
||||
Point pt = Point(scale_(point_inv.x()), scale_(point_inv.y()));
|
||||
if (isl.expoly.contains(pt))
|
||||
return true;
|
||||
}
|
||||
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()
|
||||
bool MeshClipper::has_valid_contour() const
|
||||
{
|
||||
if (!m_result)
|
||||
recalculate_triangles();
|
||||
|
||||
for (CutIsland& isl : m_result->cut_islands)
|
||||
if (isl.model_expanded.get_bounding_box().defined)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
return m_result && std::any_of(m_result->cut_islands.begin(), m_result->cut_islands.end(), [](const CutIsland& isl) { return !isl.expoly.empty(); });
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -115,8 +115,8 @@ public:
|
|||
|
||||
void pass_mouse_click(const Vec3d& pt);
|
||||
|
||||
bool contains(Vec3d point);
|
||||
bool has_valid_contour();
|
||||
bool is_projection_inside_cut(const Vec3d& point) const;
|
||||
bool has_valid_contour() const;
|
||||
|
||||
private:
|
||||
void recalculate_triangles();
|
||||
|
|
Loading…
Reference in a new issue