Merge branch 'lm_contours2'
This commit is contained in:
commit
8223f083af
@ -1353,6 +1353,7 @@ void ModelObject::synchronize_model_after_cut()
|
|||||||
if (obj->is_cut() && obj->cut_id.has_same_id(this->cut_id))
|
if (obj->is_cut() && obj->cut_id.has_same_id(this->cut_id))
|
||||||
obj->cut_id.copy(this->cut_id);
|
obj->cut_id.copy(this->cut_id);
|
||||||
}
|
}
|
||||||
|
this->invalidate_cut();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelObject::apply_cut_attributes(ModelObjectCutAttributes attributes)
|
void ModelObject::apply_cut_attributes(ModelObjectCutAttributes attributes)
|
||||||
@ -1378,7 +1379,7 @@ void ModelObject::apply_cut_attributes(ModelObjectCutAttributes attributes)
|
|||||||
void ModelObject::clone_for_cut(ModelObject** obj)
|
void ModelObject::clone_for_cut(ModelObject** obj)
|
||||||
{
|
{
|
||||||
(*obj) = ModelObject::new_clone(*this);
|
(*obj) = ModelObject::new_clone(*this);
|
||||||
(*obj)->set_model(nullptr);
|
(*obj)->set_model(this->get_model());
|
||||||
(*obj)->sla_support_points.clear();
|
(*obj)->sla_support_points.clear();
|
||||||
(*obj)->sla_drain_holes.clear();
|
(*obj)->sla_drain_holes.clear();
|
||||||
(*obj)->sla_points_status = sla::PointsStatus::NoPoints;
|
(*obj)->sla_points_status = sla::PointsStatus::NoPoints;
|
||||||
@ -1461,7 +1462,7 @@ static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelV
|
|||||||
|
|
||||||
void ModelObject::process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
void ModelObject::process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
|
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
|
||||||
std::vector<ModelObject*>& dowels, Vec3d& local_dowels_displace)
|
std::vector<ModelObject*>& dowels)
|
||||||
{
|
{
|
||||||
assert(volume->cut_info.is_connector);
|
assert(volume->cut_info.is_connector);
|
||||||
volume->cut_info.set_processed();
|
volume->cut_info.set_processed();
|
||||||
@ -1497,9 +1498,6 @@ void ModelObject::process_connector_cut(ModelVolume* volume, const Transform3d&
|
|||||||
vol->set_rotation(Vec3d::Zero());
|
vol->set_rotation(Vec3d::Zero());
|
||||||
vol->set_offset(Z, 0.0);
|
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);
|
dowels.push_back(dowel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1565,9 +1563,8 @@ void ModelObject::process_volume_cut(ModelVolume* volume, const Transform3d& ins
|
|||||||
if (attributes.has(ModelObjectCutAttribute::KeepLower))
|
if (attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||||
lower_mesh = TriangleMesh(lower_its);
|
lower_mesh = TriangleMesh(lower_its);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace)
|
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
|
||||||
{
|
{
|
||||||
// Perform cut
|
// Perform cut
|
||||||
TriangleMesh upper_mesh, lower_mesh;
|
TriangleMesh upper_mesh, lower_mesh;
|
||||||
@ -1584,31 +1581,12 @@ void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d&
|
|||||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix);
|
add_cut_volume(upper_mesh, upper, volume, cut_matrix);
|
||||||
|
|
||||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) {
|
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty())
|
||||||
add_cut_volume(lower_mesh, lower, volume, cut_matrix);
|
add_cut_volume(lower_mesh, lower, volume, cut_matrix);
|
||||||
|
|
||||||
// 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)
|
void ModelObject::reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix,
|
||||||
{
|
bool place_on_cut/* = false*/, bool flip/* = false*/)
|
||||||
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;
|
using namespace Geometry;
|
||||||
|
|
||||||
@ -1616,14 +1594,9 @@ static void reset_instance_transformation(ModelObject* object, size_t src_instan
|
|||||||
|
|
||||||
for (size_t i = 0; i < object->instances.size(); ++i) {
|
for (size_t i = 0; i < object->instances.size(); ++i) {
|
||||||
auto& obj_instance = object->instances[i];
|
auto& obj_instance = object->instances[i];
|
||||||
const Vec3d offset = obj_instance->get_offset();
|
|
||||||
const double rot_z = obj_instance->get_rotation().z();
|
const double rot_z = obj_instance->get_rotation().z();
|
||||||
|
|
||||||
obj_instance->set_transformation(Transformation());
|
obj_instance->set_transformation(Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor()));
|
||||||
|
|
||||||
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();
|
Vec3d rotation = Vec3d::Zero();
|
||||||
if (!flip && !place_on_cut) {
|
if (!flip && !place_on_cut) {
|
||||||
@ -1681,10 +1654,6 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix,
|
|||||||
const Transformation cut_transformation = Transformation(cut_matrix);
|
const Transformation cut_transformation = Transformation(cut_matrix);
|
||||||
const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset());
|
const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset());
|
||||||
|
|
||||||
// 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) {
|
for (ModelVolume* volume : volumes) {
|
||||||
volume->reset_extra_facets();
|
volume->reset_extra_facets();
|
||||||
|
|
||||||
@ -1692,10 +1661,10 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix,
|
|||||||
if (volume->cut_info.is_processed)
|
if (volume->cut_info.is_processed)
|
||||||
process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower);
|
process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower);
|
||||||
else
|
else
|
||||||
process_connector_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, dowels, local_dowels_displace);
|
process_connector_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, dowels);
|
||||||
}
|
}
|
||||||
else if (!volume->mesh().empty())
|
else if (!volume->mesh().empty())
|
||||||
process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, local_displace);
|
process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post-process cut parts
|
// Post-process cut parts
|
||||||
@ -1708,31 +1677,22 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix,
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper->volumes.empty()) {
|
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper->volumes.empty()) {
|
||||||
invalidate_translations(upper, instances[instance]);
|
|
||||||
|
|
||||||
reset_instance_transformation(upper, instance, cut_matrix,
|
reset_instance_transformation(upper, instance, cut_matrix,
|
||||||
attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
|
attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
|
||||||
attributes.has(ModelObjectCutAttribute::FlipUpper),
|
attributes.has(ModelObjectCutAttribute::FlipUpper));
|
||||||
local_displace);
|
|
||||||
res.push_back(upper);
|
res.push_back(upper);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower->volumes.empty()) {
|
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower->volumes.empty()) {
|
||||||
invalidate_translations(lower, instances[instance]);
|
|
||||||
|
|
||||||
reset_instance_transformation(lower, instance, cut_matrix,
|
reset_instance_transformation(lower, instance, cut_matrix,
|
||||||
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
|
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
|
||||||
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) ? true : attributes.has(ModelObjectCutAttribute::FlipLower));
|
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || attributes.has(ModelObjectCutAttribute::FlipLower));
|
||||||
res.push_back(lower);
|
res.push_back(lower);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) {
|
if (attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) {
|
||||||
for (auto dowel : dowels) {
|
for (auto dowel : dowels) {
|
||||||
invalidate_translations(dowel, instances[instance]);
|
reset_instance_transformation(dowel, instance, Transform3d::Identity());
|
||||||
|
|
||||||
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;
|
dowel->name += "-Dowel-" + dowel->volumes[0]->name;
|
||||||
res.push_back(dowel);
|
res.push_back(dowel);
|
||||||
}
|
}
|
||||||
@ -2669,14 +2629,6 @@ bool model_has_multi_part_objects(const Model &model)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool model_has_connectors(const Model &model)
|
|
||||||
{
|
|
||||||
for (const ModelObject *model_object : model.objects)
|
|
||||||
if (!model_object->cut_connectors.empty())
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool model_has_advanced_features(const Model &model)
|
bool model_has_advanced_features(const Model &model)
|
||||||
{
|
{
|
||||||
auto config_is_advanced = [](const ModelConfig &config) {
|
auto config_is_advanced = [](const ModelConfig &config) {
|
||||||
|
@ -467,18 +467,28 @@ public:
|
|||||||
void invalidate_cut();
|
void invalidate_cut();
|
||||||
// delete volumes which are marked as connector for this object
|
// delete volumes which are marked as connector for this object
|
||||||
void delete_connectors();
|
void delete_connectors();
|
||||||
void synchronize_model_after_cut();
|
|
||||||
void apply_cut_attributes(ModelObjectCutAttributes attributes);
|
|
||||||
void clone_for_cut(ModelObject **obj);
|
void clone_for_cut(ModelObject **obj);
|
||||||
|
|
||||||
|
void apply_cut_attributes(ModelObjectCutAttributes attributes);
|
||||||
|
private:
|
||||||
|
// FIXME: These functions would best not be here at all. It might make sense to separate the
|
||||||
|
// cut-related methods elsewhere. Same holds for cut_connectors data member, which is currently
|
||||||
|
// just a temporary variable used by cut gizmo only.
|
||||||
|
void synchronize_model_after_cut();
|
||||||
|
|
||||||
void process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
void process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
|
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
|
||||||
std::vector<ModelObject*>& dowels, Vec3d& local_dowels_displace);
|
std::vector<ModelObject*>& dowels);
|
||||||
void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
|
void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
|
||||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower);
|
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower);
|
||||||
void process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
void process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||||
ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh);
|
ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh);
|
||||||
void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace);
|
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower);
|
||||||
|
public:
|
||||||
|
static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix,
|
||||||
|
bool place_on_cut = false, bool flip = false);
|
||||||
|
|
||||||
ModelObjectPtrs cut(size_t instance, const Transform3d&cut_matrix, ModelObjectCutAttributes attributes);
|
ModelObjectPtrs cut(size_t instance, const Transform3d&cut_matrix, ModelObjectCutAttributes attributes);
|
||||||
void split(ModelObjectPtrs*new_objects);
|
void split(ModelObjectPtrs*new_objects);
|
||||||
void merge();
|
void merge();
|
||||||
@ -1391,8 +1401,6 @@ bool model_has_parameter_modifiers_in_objects(const Model& model);
|
|||||||
// If the model has multi-part objects, then it is currently not supported by the SLA mode.
|
// If the model has multi-part objects, then it is currently not supported by the SLA mode.
|
||||||
// Either the model cannot be loaded, or a SLA printer has to be activated.
|
// Either the model cannot be loaded, or a SLA printer has to be activated.
|
||||||
bool model_has_multi_part_objects(const Model &model);
|
bool model_has_multi_part_objects(const Model &model);
|
||||||
// If the model has objects with cut connectrs, then it is currently not supported by the SLA mode.
|
|
||||||
bool model_has_connectors(const Model& model);
|
|
||||||
// If the model has advanced features, then it cannot be processed in simple mode.
|
// If the model has advanced features, then it cannot be processed in simple mode.
|
||||||
bool model_has_advanced_features(const Model &model);
|
bool model_has_advanced_features(const Model &model);
|
||||||
|
|
||||||
|
@ -3023,22 +3023,6 @@ bool GUI_App::may_switch_to_SLA_preset(const wxString& caption)
|
|||||||
caption);
|
caption);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
if (model_has_multi_part_objects(model())) {
|
|
||||||
show_info(nullptr,
|
|
||||||
_L("It's impossible to print multi-part object(s) with SLA technology.") + "\n\n" +
|
|
||||||
_L("Please check your object list before preset changing."),
|
|
||||||
caption);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (model_has_connectors(model())) {
|
|
||||||
show_info(nullptr,
|
|
||||||
_L("SLA technology doesn't support cut with connectors") + "\n\n" +
|
|
||||||
_L("Please check your object list before preset changing."),
|
|
||||||
caption);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +250,8 @@ std::string GLGizmoCut3D::get_tooltip() const
|
|||||||
|
|
||||||
if (!m_dragging && m_hover_id == CutPlane)
|
if (!m_dragging && m_hover_id == CutPlane)
|
||||||
return _u8L("Click to flip the cut plane\n"
|
return _u8L("Click to flip the cut plane\n"
|
||||||
"Drag to move the cut plane");
|
"Drag to move the cut plane\n"
|
||||||
|
"Right-click a part to assign it to the other side");
|
||||||
|
|
||||||
if (tooltip.empty() && (m_hover_id == X || m_hover_id == Y)) {
|
if (tooltip.empty() && (m_hover_id == X || m_hover_id == Y)) {
|
||||||
std::string axis = m_hover_id == X ? "X" : "Y";
|
std::string axis = m_hover_id == X ? "X" : "Y";
|
||||||
@ -289,11 +290,28 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event)
|
|||||||
gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown());
|
gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown());
|
||||||
}
|
}
|
||||||
else if (m_hover_id == CutPlane) {
|
else if (m_hover_id == CutPlane) {
|
||||||
if (mouse_event.LeftDown())
|
if (mouse_event.LeftDown()) {
|
||||||
m_was_cut_plane_dragged = false;
|
m_was_cut_plane_dragged = m_was_contour_selected = false;
|
||||||
else if (mouse_event.LeftUp() && !m_was_cut_plane_dragged)
|
|
||||||
|
// disable / enable current contour
|
||||||
|
Vec3d pos;
|
||||||
|
Vec3d pos_world;
|
||||||
|
m_was_contour_selected = unproject_on_cut_plane(mouse_pos.cast<double>(), pos, pos_world, false);
|
||||||
|
if (m_was_contour_selected) {
|
||||||
|
// Following would inform the clipper about the mouse click, so it can
|
||||||
|
// toggle the respective contour as disabled.
|
||||||
|
//m_c->object_clipper()->pass_mouse_click(pos_world);
|
||||||
|
//process_contours();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (mouse_event.LeftUp() && !m_was_cut_plane_dragged && !m_was_contour_selected)
|
||||||
flip_cut_plane();
|
flip_cut_plane();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_part_selection.valid())
|
||||||
|
m_parent.toggle_model_objects_visibility(false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,6 +352,15 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (mouse_event.RightDown()) {
|
else if (mouse_event.RightDown()) {
|
||||||
|
if (! m_connectors_editing && mouse_event.GetModifiers() == wxMOD_NONE) {
|
||||||
|
// Check the internal part raycasters.
|
||||||
|
if (! m_part_selection.valid())
|
||||||
|
process_contours();
|
||||||
|
m_part_selection.toggle_selection(mouse_pos);
|
||||||
|
check_and_update_connectors_state(); // after a contour is deactivated, its connectors are inside the object
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_parent.get_selection().get_object_idx() != -1 &&
|
if (m_parent.get_selection().get_object_idx() != -1 &&
|
||||||
gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) {
|
gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) {
|
||||||
// we need to set the following right up as processed to avoid showing
|
// we need to set the following right up as processed to avoid showing
|
||||||
@ -434,6 +461,7 @@ void GLGizmoCut3D::update_clipper()
|
|||||||
void GLGizmoCut3D::set_center(const Vec3d& center, bool update_tbb /*=false*/)
|
void GLGizmoCut3D::set_center(const Vec3d& center, bool update_tbb /*=false*/)
|
||||||
{
|
{
|
||||||
set_center_pos(center, update_tbb);
|
set_center_pos(center, update_tbb);
|
||||||
|
check_and_update_connectors_state();
|
||||||
update_clipper();
|
update_clipper();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,6 +587,8 @@ void GLGizmoCut3D::render_move_center_input(int axis)
|
|||||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction);
|
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction);
|
||||||
set_center(move, true);
|
set_center(move, true);
|
||||||
m_ar_plane_center = m_plane_center;
|
m_ar_plane_center = m_plane_center;
|
||||||
|
|
||||||
|
reset_cut_by_contours();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -865,6 +895,7 @@ void GLGizmoCut3D::on_set_state()
|
|||||||
// initiate archived values
|
// initiate archived values
|
||||||
m_ar_plane_center = m_plane_center;
|
m_ar_plane_center = m_plane_center;
|
||||||
m_start_dragging_m = m_rotation_m;
|
m_start_dragging_m = m_rotation_m;
|
||||||
|
reset_cut_by_contours();
|
||||||
|
|
||||||
m_parent.request_extra_frame();
|
m_parent.request_extra_frame();
|
||||||
}
|
}
|
||||||
@ -876,6 +907,13 @@ void GLGizmoCut3D::on_set_state()
|
|||||||
m_selected.clear();
|
m_selected.clear();
|
||||||
m_parent.set_use_color_clip_plane(false);
|
m_parent.set_use_color_clip_plane(false);
|
||||||
m_c->selection_info()->set_use_shift(false);
|
m_c->selection_info()->set_use_shift(false);
|
||||||
|
|
||||||
|
// Make sure that the part selection data are released when the gizmo is closed.
|
||||||
|
// The CallAfter is needed because in perform_cut, the gizmo is closed BEFORE
|
||||||
|
// the cut is performed (because of undo/redo snapshots), so the data would
|
||||||
|
// be deleted prematurely.
|
||||||
|
if (m_part_selection.valid())
|
||||||
|
wxGetApp().CallAfter([this]() { m_part_selection = PartSelection(); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1089,6 +1127,8 @@ void GLGizmoCut3D::dragging_grabber_z(const GLGizmoBase::UpdateData &data)
|
|||||||
projection = m_snap_step * std::round(projection / m_snap_step);
|
projection = m_snap_step * std::round(projection / m_snap_step);
|
||||||
|
|
||||||
const Vec3d shift = starting_vec * projection;
|
const Vec3d shift = starting_vec * projection;
|
||||||
|
if (shift != Vec3d::Zero())
|
||||||
|
reset_cut_by_contours();
|
||||||
|
|
||||||
// move cut plane center
|
// move cut plane center
|
||||||
set_center(m_plane_center + shift, true);
|
set_center(m_plane_center + shift, true);
|
||||||
@ -1126,6 +1166,9 @@ void GLGizmoCut3D::dragging_grabber_xy(const GLGizmoBase::UpdateData &data)
|
|||||||
if (m_hover_id == X)
|
if (m_hover_id == X)
|
||||||
theta += 0.5 * PI;
|
theta += 0.5 * PI;
|
||||||
|
|
||||||
|
if (!is_approx(theta, 0.0))
|
||||||
|
reset_cut_by_contours();
|
||||||
|
|
||||||
Vec3d rotation = Vec3d::Zero();
|
Vec3d rotation = Vec3d::Zero();
|
||||||
rotation[m_hover_id] = theta;
|
rotation[m_hover_id] = theta;
|
||||||
|
|
||||||
@ -1166,6 +1209,7 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data)
|
|||||||
dragging_grabber_xy(data);
|
dragging_grabber_xy(data);
|
||||||
else if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual)
|
else if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual)
|
||||||
dragging_connector(data);
|
dragging_connector(data);
|
||||||
|
check_and_update_connectors_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGizmoCut3D::on_start_dragging()
|
void GLGizmoCut3D::on_start_dragging()
|
||||||
@ -1191,6 +1235,7 @@ void GLGizmoCut3D::on_stop_dragging()
|
|||||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction);
|
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction);
|
||||||
m_ar_plane_center = m_plane_center;
|
m_ar_plane_center = m_plane_center;
|
||||||
}
|
}
|
||||||
|
//check_and_update_connectors_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool update_tbb /*=false*/)
|
void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool update_tbb /*=false*/)
|
||||||
@ -1270,6 +1315,7 @@ void GLGizmoCut3D::update_bb()
|
|||||||
m_bounding_box = box;
|
m_bounding_box = box;
|
||||||
|
|
||||||
invalidate_cut_plane();
|
invalidate_cut_plane();
|
||||||
|
reset_cut_by_contours();
|
||||||
|
|
||||||
m_max_pos = box.max;
|
m_max_pos = box.max;
|
||||||
m_min_pos = box.min;
|
m_min_pos = box.min;
|
||||||
@ -1353,11 +1399,239 @@ void GLGizmoCut3D::render_clipper_cut()
|
|||||||
{
|
{
|
||||||
if (! m_connectors_editing)
|
if (! m_connectors_editing)
|
||||||
::glDisable(GL_DEPTH_TEST);
|
::glDisable(GL_DEPTH_TEST);
|
||||||
m_c->object_clipper()->render_cut();
|
|
||||||
|
GLboolean cull_face = GL_FALSE;
|
||||||
|
::glGetBooleanv(GL_CULL_FACE, &cull_face);
|
||||||
|
::glDisable(GL_CULL_FACE);
|
||||||
|
m_c->object_clipper()->render_cut(m_part_selection.get_ignored_contours_ptr());
|
||||||
|
if (cull_face)
|
||||||
|
::glEnable(GL_CULL_FACE);
|
||||||
|
|
||||||
if (! m_connectors_editing)
|
if (! m_connectors_editing)
|
||||||
::glEnable(GL_DEPTH_TEST);
|
::glEnable(GL_DEPTH_TEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx_in, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc)
|
||||||
|
{
|
||||||
|
m_model = Model();
|
||||||
|
m_model.add_object(*mo);
|
||||||
|
ModelObjectPtrs cut_part_ptrs = m_model.objects.front()->cut(instance_idx_in, cut_matrix,
|
||||||
|
ModelObjectCutAttribute::KeepUpper |
|
||||||
|
ModelObjectCutAttribute::KeepLower |
|
||||||
|
ModelObjectCutAttribute::KeepAsParts);
|
||||||
|
assert(cut_part_ptrs.size() == 1);
|
||||||
|
m_model = Model();
|
||||||
|
m_model.add_object(*cut_part_ptrs.front());
|
||||||
|
|
||||||
|
m_instance_idx = instance_idx_in;
|
||||||
|
|
||||||
|
const ModelVolumePtrs& volumes = model_object()->volumes;
|
||||||
|
|
||||||
|
// split to parts
|
||||||
|
for (int id = int(volumes.size())-1; id >= 0; id--)
|
||||||
|
if (volumes[id]->is_splittable())
|
||||||
|
volumes[id]->split(1);
|
||||||
|
|
||||||
|
m_parts.clear();
|
||||||
|
for (const ModelVolume* volume : volumes) {
|
||||||
|
assert(volume != nullptr);
|
||||||
|
m_parts.emplace_back(Part{GLModel(), MeshRaycaster(volume->mesh()), true});
|
||||||
|
m_parts.back().glmodel.set_color({ 0.f, 0.f, 1.f, 1.f });
|
||||||
|
m_parts.back().glmodel.init_from(volume->mesh());
|
||||||
|
|
||||||
|
// Now check whether this part is below or above the plane.
|
||||||
|
Transform3d tr = (model_object()->instances[m_instance_idx]->get_matrix() * volume->get_matrix()).inverse();
|
||||||
|
Vec3f pos = (tr * center).cast<float>();
|
||||||
|
Vec3f norm = (tr.linear().inverse().transpose() * normal).cast<float>();
|
||||||
|
for (const Vec3f& v : volume->mesh().its.vertices) {
|
||||||
|
double p = (v - pos).dot(norm);
|
||||||
|
if (std::abs(p) > EPSILON) {
|
||||||
|
m_parts.back().selected = p > 0.;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now go through the contours and create a map from contours to parts.
|
||||||
|
m_contour_points.clear();
|
||||||
|
m_contour_to_parts.clear();
|
||||||
|
m_debug_pts = std::vector<std::vector<Vec3d>>(m_parts.size(), std::vector<Vec3d>());
|
||||||
|
if (std::vector<Vec3d> pts = oc.point_per_contour();! pts.empty()) {
|
||||||
|
|
||||||
|
m_contour_to_parts.resize(pts.size());
|
||||||
|
|
||||||
|
for (size_t pt_idx=0; pt_idx<pts.size(); ++pt_idx) {
|
||||||
|
const Vec3d& pt = pts[pt_idx];
|
||||||
|
const Vec3d dir = (center-pt).dot(normal) * normal;
|
||||||
|
m_contour_points.emplace_back(dir + pt); // the result is in world coordinates.
|
||||||
|
|
||||||
|
// Now, cast a ray from every contour point and see which volumes of the ones above
|
||||||
|
// the plane are hit from the inside.
|
||||||
|
for (size_t part_id=0; part_id<m_parts.size(); ++part_id) {
|
||||||
|
const AABBMesh& aabb = m_parts[part_id].raycaster.get_aabb_mesh();
|
||||||
|
const Transform3d& tr = (translation_transform(model_object()->instances[m_instance_idx]->get_offset()) * translation_transform(model_object()->volumes[part_id]->get_offset())).inverse();
|
||||||
|
for (double d : {-1., 1.}) {
|
||||||
|
const Vec3d dir_mesh = d * tr.linear().inverse().transpose() * normal;
|
||||||
|
const Vec3d src = tr * (m_contour_points[pt_idx] + d*0.01 * normal);
|
||||||
|
AABBMesh::hit_result hit = aabb.query_ray_hit(src, dir_mesh);
|
||||||
|
|
||||||
|
m_debug_pts[part_id].emplace_back(src);
|
||||||
|
|
||||||
|
if (hit.is_inside()) {
|
||||||
|
// This part belongs to this point.
|
||||||
|
if (d == 1.)
|
||||||
|
m_contour_to_parts[pt_idx].first.emplace_back(part_id);
|
||||||
|
else
|
||||||
|
m_contour_to_parts[pt_idx].second.emplace_back(part_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
m_valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLGizmoCut3D::PartSelection::render(const Vec3d* normal, GLModel& sphere_model)
|
||||||
|
{
|
||||||
|
if (! valid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||||
|
|
||||||
|
if (GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light")) {
|
||||||
|
shader->start_using();
|
||||||
|
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||||
|
shader->set_uniform("emission_factor", 0.f);
|
||||||
|
|
||||||
|
// FIXME: Cache the transforms.
|
||||||
|
|
||||||
|
const Vec3d inst_offset = model_object()->instances[m_instance_idx]->get_offset();
|
||||||
|
const Transform3d view_inst_matrix= camera.get_view_matrix() * translation_transform(inst_offset);
|
||||||
|
|
||||||
|
const bool is_looking_forward = normal && camera.get_dir_forward().dot(*normal) < 0.05;
|
||||||
|
|
||||||
|
for (size_t id=0; id<m_parts.size(); ++id) {
|
||||||
|
if (normal && (( is_looking_forward && m_parts[id].selected) ||
|
||||||
|
(!is_looking_forward && !m_parts[id].selected) ) )
|
||||||
|
continue;
|
||||||
|
const Vec3d volume_offset = model_object()->volumes[id]->get_offset();
|
||||||
|
shader->set_uniform("view_model_matrix", view_inst_matrix * translation_transform(volume_offset));
|
||||||
|
m_parts[id].glmodel.set_color(m_parts[id].selected ? UPPER_PART_COLOR : LOWER_PART_COLOR);
|
||||||
|
m_parts[id].glmodel.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
shader->stop_using();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// { // Debugging render:
|
||||||
|
|
||||||
|
// static int idx = -1;
|
||||||
|
// ImGui::Begin("DEBUG");
|
||||||
|
// for (int i=0; i<m_parts.size(); ++i)
|
||||||
|
// if (ImGui::Button(std::to_string(i).c_str()))
|
||||||
|
// idx = i;
|
||||||
|
// if (idx >= m_parts.size())
|
||||||
|
// idx = -1;
|
||||||
|
// ImGui::End();
|
||||||
|
|
||||||
|
// ::glDisable(GL_DEPTH_TEST);
|
||||||
|
// if (valid()) {
|
||||||
|
// for (size_t i=0; i<m_contour_points.size(); ++i) {
|
||||||
|
// const Vec3d& pt = m_contour_points[i];
|
||||||
|
// ColorRGBA col = ColorRGBA::GREEN();
|
||||||
|
|
||||||
|
// bool red = false;
|
||||||
|
// bool yellow = false;
|
||||||
|
// for (size_t j=0; j<m_contour_to_parts[i].first.size(); ++j) {
|
||||||
|
// red |= m_parts[m_contour_to_parts[i].first[j]].selected;
|
||||||
|
// yellow |= m_parts[m_contour_to_parts[i].second[j]].selected;
|
||||||
|
// }
|
||||||
|
// if (red)
|
||||||
|
// col = ColorRGBA::RED();
|
||||||
|
// if (yellow)
|
||||||
|
// col = ColorRGBA::YELLOW();
|
||||||
|
|
||||||
|
// GLGizmoCut3D::render_model(sphere_model, col, camera.get_view_matrix() * translation_transform(pt));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (idx != -1) {
|
||||||
|
// render_model(m_parts[idx].glmodel, ColorRGBA::RED(), camera.get_view_matrix());
|
||||||
|
// for (const Vec3d& pt : m_debug_pts[idx]) {
|
||||||
|
// render_model(sphere_model, ColorRGBA::GREEN(), camera.get_view_matrix() * translation_transform(pt));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ::glEnable(GL_DEPTH_TEST);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool GLGizmoCut3D::PartSelection::is_one_object() const
|
||||||
|
{
|
||||||
|
// In theory, the implementation could be just this:
|
||||||
|
// return m_contour_to_parts.size() == m_ignored_contours.size();
|
||||||
|
// However, this would require that the part-contour correspondence works
|
||||||
|
// flawlessly. Because it is currently not always so for self-intersecting
|
||||||
|
// objects, let's better check the parts itself:
|
||||||
|
if (m_parts.size() < 2)
|
||||||
|
return true;
|
||||||
|
return std::all_of(m_parts.begin(), m_parts.end(), [this](const Part& part) {
|
||||||
|
return part.selected == m_parts.front().selected;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void GLGizmoCut3D::PartSelection::toggle_selection(const Vec2d& mouse_pos)
|
||||||
|
{
|
||||||
|
// FIXME: Cache the transforms.
|
||||||
|
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||||
|
const Vec3d& camera_pos = camera.get_position();
|
||||||
|
|
||||||
|
Vec3f pos;
|
||||||
|
Vec3f normal;
|
||||||
|
|
||||||
|
std::vector<std::pair<size_t, double>> hits_id_and_sqdist;
|
||||||
|
|
||||||
|
for (size_t id=0; id<m_parts.size(); ++id) {
|
||||||
|
const Vec3d volume_offset = model_object()->volumes[id]->get_offset();
|
||||||
|
Transform3d tr = translation_transform(model_object()->instances[m_instance_idx]->get_offset()) * translation_transform(model_object()->volumes[id]->get_offset());
|
||||||
|
if (m_parts[id].raycaster.unproject_on_mesh(mouse_pos, tr, camera, pos, normal)) {
|
||||||
|
hits_id_and_sqdist.emplace_back(id, (camera_pos - tr*(pos.cast<double>())).squaredNorm());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! hits_id_and_sqdist.empty()) {
|
||||||
|
size_t id = std::min_element(hits_id_and_sqdist.begin(), hits_id_and_sqdist.end(),
|
||||||
|
[](const std::pair<size_t, double>& a, const std::pair<size_t, double>& b) { return a.second < b.second; })->first;
|
||||||
|
m_parts[id].selected = ! m_parts[id].selected;
|
||||||
|
|
||||||
|
// And now recalculate the contours which should be ignored.
|
||||||
|
m_ignored_contours.clear();
|
||||||
|
size_t cont_id = 0;
|
||||||
|
for (const auto& [parts_above, parts_below] : m_contour_to_parts) {
|
||||||
|
for (size_t upper : parts_above) {
|
||||||
|
bool upper_sel = m_parts[upper].selected;
|
||||||
|
if (std::find_if(parts_below.begin(), parts_below.end(), [this, &upper_sel](const size_t& i) { return m_parts[i].selected == upper_sel; }) != parts_below.end()) {
|
||||||
|
m_ignored_contours.emplace_back(cont_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++cont_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLGizmoCut3D::PartSelection::turn_over_selection()
|
||||||
|
{
|
||||||
|
for (Part& part : m_parts)
|
||||||
|
part.selected = !part.selected;
|
||||||
|
}
|
||||||
|
|
||||||
void GLGizmoCut3D::on_render()
|
void GLGizmoCut3D::on_render()
|
||||||
{
|
{
|
||||||
if (m_state == On) {
|
if (m_state == On) {
|
||||||
@ -1366,6 +1640,7 @@ void GLGizmoCut3D::on_render()
|
|||||||
m_c->selection_info()->set_use_shift(true);
|
m_c->selection_info()->set_use_shift(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
update_clipper();
|
update_clipper();
|
||||||
|
|
||||||
init_picking_models();
|
init_picking_models();
|
||||||
@ -1374,6 +1649,11 @@ void GLGizmoCut3D::on_render()
|
|||||||
|
|
||||||
render_connectors();
|
render_connectors();
|
||||||
|
|
||||||
|
if (!m_connectors_editing)
|
||||||
|
m_part_selection.render(nullptr, m_sphere.model);
|
||||||
|
else
|
||||||
|
m_part_selection.render(&m_cut_normal, m_sphere.model);
|
||||||
|
|
||||||
render_clipper_cut();
|
render_clipper_cut();
|
||||||
|
|
||||||
if (!m_hide_cut_plane && !m_connectors_editing) {
|
if (!m_hide_cut_plane && !m_connectors_editing) {
|
||||||
@ -1483,7 +1763,7 @@ void GLGizmoCut3D::apply_selected_connectors(std::function<void(size_t idx)> app
|
|||||||
for (size_t idx = 0; idx < m_selected.size(); idx++)
|
for (size_t idx = 0; idx < m_selected.size(); idx++)
|
||||||
if (m_selected[idx])
|
if (m_selected[idx])
|
||||||
apply_fn(idx);
|
apply_fn(idx);
|
||||||
|
check_and_update_connectors_state();
|
||||||
update_raycasters_for_picking_transform();
|
update_raycasters_for_picking_transform();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1587,6 +1867,9 @@ void GLGizmoCut3D::reset_cut_plane()
|
|||||||
set_center(m_bb_center);
|
set_center(m_bb_center);
|
||||||
m_start_dragging_m = m_rotation_m = Transform3d::Identity();
|
m_start_dragging_m = m_rotation_m = Transform3d::Identity();
|
||||||
m_ar_plane_center = m_plane_center;
|
m_ar_plane_center = m_plane_center;
|
||||||
|
|
||||||
|
reset_cut_by_contours();
|
||||||
|
m_parent.request_extra_frame();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGizmoCut3D::invalidate_cut_plane()
|
void GLGizmoCut3D::invalidate_cut_plane()
|
||||||
@ -1621,6 +1904,31 @@ void GLGizmoCut3D::flip_cut_plane()
|
|||||||
m_start_dragging_m = m_rotation_m;
|
m_start_dragging_m = m_rotation_m;
|
||||||
|
|
||||||
update_clipper();
|
update_clipper();
|
||||||
|
m_part_selection.turn_over_selection();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLGizmoCut3D::reset_cut_by_contours()
|
||||||
|
{
|
||||||
|
m_part_selection = PartSelection();
|
||||||
|
|
||||||
|
const Selection& selection = m_parent.get_selection();
|
||||||
|
const ModelObjectPtrs& model_objects = selection.get_model()->objects;
|
||||||
|
m_parent.toggle_model_objects_visibility(true, model_objects[selection.get_object_idx()], selection.get_instance_idx());
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLGizmoCut3D::process_contours()
|
||||||
|
{
|
||||||
|
reset_cut_by_contours();
|
||||||
|
|
||||||
|
const Selection& selection = m_parent.get_selection();
|
||||||
|
const ModelObjectPtrs& model_objects = selection.get_model()->objects;
|
||||||
|
|
||||||
|
wxBusyCursor wait;
|
||||||
|
const int instance_idx = selection.get_instance_idx();
|
||||||
|
const int object_idx = selection.get_object_idx();
|
||||||
|
|
||||||
|
m_part_selection = PartSelection(model_objects[object_idx], get_cut_matrix(selection), instance_idx, m_plane_center, m_cut_normal, *m_c->object_clipper());
|
||||||
|
m_parent.toggle_model_objects_visibility(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGizmoCut3D::render_flip_plane_button(bool disable_pred /*=false*/)
|
void GLGizmoCut3D::render_flip_plane_button(bool disable_pred /*=false*/)
|
||||||
@ -1701,7 +2009,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors)
|
|||||||
|
|
||||||
add_vertical_scaled_interval(0.75f);
|
add_vertical_scaled_interval(0.75f);
|
||||||
|
|
||||||
m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower || m_keep_as_parts);
|
m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower || m_keep_as_parts || (m_part_selection.valid() && m_part_selection.is_one_object()));
|
||||||
if (m_imgui->button(has_connectors ? _L("Edit connectors") : _L("Add connectors")))
|
if (m_imgui->button(has_connectors ? _L("Edit connectors") : _L("Add connectors")))
|
||||||
set_connectors_editing(true);
|
set_connectors_editing(true);
|
||||||
m_imgui->disabled_end();
|
m_imgui->disabled_end();
|
||||||
@ -1784,9 +2092,12 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors)
|
|||||||
|
|
||||||
add_vertical_scaled_interval(0.75f);
|
add_vertical_scaled_interval(0.75f);
|
||||||
|
|
||||||
m_imgui->disabled_begin(has_connectors);
|
m_imgui->disabled_begin(has_connectors || m_part_selection.valid());
|
||||||
ImGuiWrapper::text(_L("Cut into") + ":");
|
ImGuiWrapper::text(_L("Cut into") + ":");
|
||||||
|
|
||||||
|
if (m_part_selection.valid())
|
||||||
|
m_keep_as_parts = false;
|
||||||
|
|
||||||
add_horizontal_scaled_interval(1.2f);
|
add_horizontal_scaled_interval(1.2f);
|
||||||
// TRN CutGizmo: RadioButton Cut into ...
|
// TRN CutGizmo: RadioButton Cut into ...
|
||||||
if (m_imgui->radio_button(_L("Objects"), !m_keep_as_parts))
|
if (m_imgui->radio_button(_L("Objects"), !m_keep_as_parts))
|
||||||
@ -1904,7 +2215,7 @@ void GLGizmoCut3D::render_input_window_warning() const
|
|||||||
{
|
{
|
||||||
if (m_is_contour_changed)
|
if (m_is_contour_changed)
|
||||||
return;
|
return;
|
||||||
if (m_has_invalid_connector) {
|
if (! m_invalid_connectors_idxs.empty()) {
|
||||||
wxString out = wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected") + ":";
|
wxString out = wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected") + ":";
|
||||||
if (m_info_stats.outside_cut_contour > size_t(0))
|
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),
|
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),
|
||||||
@ -1949,7 +2260,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit)
|
|||||||
bool GLGizmoCut3D::is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos)
|
bool GLGizmoCut3D::is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos)
|
||||||
{
|
{
|
||||||
// check if connector pos is out of clipping plane
|
// check if connector pos is out of clipping plane
|
||||||
if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(cur_pos)) {
|
if (m_c->object_clipper() && m_c->object_clipper()->is_projection_inside_cut(cur_pos) == -1) {
|
||||||
m_info_stats.outside_cut_contour++;
|
m_info_stats.outside_cut_contour++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1974,10 +2285,19 @@ bool GLGizmoCut3D::is_outside_of_cut_contour(size_t idx, const CutConnectors& co
|
|||||||
}
|
}
|
||||||
its_transform(mesh, translation_transform(cur_pos) * m_rotation_m);
|
its_transform(mesh, translation_transform(cur_pos) * m_rotation_m);
|
||||||
|
|
||||||
for (auto vertex : vertices) {
|
for (const Vec3f& vertex : vertices) {
|
||||||
if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(vertex.cast<double>())) {
|
if (m_c->object_clipper()) {
|
||||||
m_info_stats.outside_cut_contour++;
|
int contour_idx = m_c->object_clipper()->is_projection_inside_cut(vertex.cast<double>());
|
||||||
return true;
|
bool is_invalid = (contour_idx == -1);
|
||||||
|
if (m_part_selection.valid() && ! is_invalid) {
|
||||||
|
assert(contour_idx >= 0);
|
||||||
|
const std::vector<size_t>& ignored = *(m_part_selection.get_ignored_contours_ptr());
|
||||||
|
is_invalid = (std::find(ignored.begin(), ignored.end(), size_t(contour_idx)) != ignored.end());
|
||||||
|
}
|
||||||
|
if (is_invalid) {
|
||||||
|
m_info_stats.outside_cut_contour++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2016,6 +2336,27 @@ bool GLGizmoCut3D::is_conflict_for_connector(size_t idx, const CutConnectors& co
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GLGizmoCut3D::check_and_update_connectors_state()
|
||||||
|
{
|
||||||
|
m_info_stats.invalidate();
|
||||||
|
m_invalid_connectors_idxs.clear();
|
||||||
|
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());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < connectors.size(); ++i) {
|
||||||
|
const CutConnector& connector = connectors[i];
|
||||||
|
Vec3d pos = connector.pos + instance_offset + sla_shift * Vec3d::UnitZ(); // recalculate connector position to world position
|
||||||
|
if (is_conflict_for_connector(i, connectors, pos))
|
||||||
|
m_invalid_connectors_idxs.emplace_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GLGizmoCut3D::render_connectors()
|
void GLGizmoCut3D::render_connectors()
|
||||||
{
|
{
|
||||||
::glEnable(GL_DEPTH_TEST);
|
::glEnable(GL_DEPTH_TEST);
|
||||||
@ -2041,9 +2382,6 @@ void GLGizmoCut3D::render_connectors()
|
|||||||
const Vec3d& instance_offset = mi->get_offset();
|
const Vec3d& instance_offset = mi->get_offset();
|
||||||
const double sla_shift = double(m_c->selection_info()->get_sla_shift());
|
const double sla_shift = double(m_c->selection_info()->get_sla_shift());
|
||||||
|
|
||||||
m_has_invalid_connector = false;
|
|
||||||
m_info_stats.invalidate();
|
|
||||||
|
|
||||||
const bool looking_forward = is_looking_forward();
|
const bool looking_forward = is_looking_forward();
|
||||||
|
|
||||||
for (size_t i = 0; i < connectors.size(); ++i) {
|
for (size_t i = 0; i < connectors.size(); ++i) {
|
||||||
@ -2054,11 +2392,10 @@ void GLGizmoCut3D::render_connectors()
|
|||||||
Vec3d pos = connector.pos + instance_offset + sla_shift * Vec3d::UnitZ();
|
Vec3d pos = connector.pos + instance_offset + sla_shift * Vec3d::UnitZ();
|
||||||
|
|
||||||
// First decide about the color of the point.
|
// First decide about the color of the point.
|
||||||
const bool conflict_connector = is_conflict_for_connector(i, connectors, pos);
|
assert(std::is_sorted(m_invalid_connectors_idxs.begin(), m_invalid_connectors_idxs.end()));
|
||||||
if (conflict_connector) {
|
const bool conflict_connector = std::binary_search(m_invalid_connectors_idxs.begin(), m_invalid_connectors_idxs.end(), i);
|
||||||
m_has_invalid_connector = true;
|
if (conflict_connector)
|
||||||
render_color = CONNECTOR_ERR_COLOR;
|
render_color = CONNECTOR_ERR_COLOR;
|
||||||
}
|
|
||||||
else // default connector color
|
else // default connector color
|
||||||
render_color = connector.attribs.type == CutConnectorType::Dowel ? DOWEL_COLOR : PLAG_COLOR;
|
render_color = connector.attribs.type == CutConnectorType::Dowel ? DOWEL_COLOR : PLAG_COLOR;
|
||||||
|
|
||||||
@ -2098,8 +2435,10 @@ void GLGizmoCut3D::render_connectors()
|
|||||||
|
|
||||||
bool GLGizmoCut3D::can_perform_cut() const
|
bool GLGizmoCut3D::can_perform_cut() const
|
||||||
{
|
{
|
||||||
if (m_has_invalid_connector || (!m_keep_upper && !m_keep_lower) || m_connectors_editing)
|
if (! m_invalid_connectors_idxs.empty() || (!m_keep_upper && !m_keep_lower) || m_connectors_editing)
|
||||||
return false;
|
return false;
|
||||||
|
if (m_part_selection.valid())
|
||||||
|
return ! m_part_selection.is_one_object();
|
||||||
|
|
||||||
return true;// has_valid_contour();
|
return true;// has_valid_contour();
|
||||||
}
|
}
|
||||||
@ -2132,6 +2471,24 @@ void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, bool &create_dowel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Transform3d GLGizmoCut3D::get_cut_matrix(const Selection& selection)
|
||||||
|
{
|
||||||
|
const int instance_idx = selection.get_instance_idx();
|
||||||
|
const int object_idx = selection.get_object_idx();
|
||||||
|
ModelObject* mo = selection.get_model()->objects[object_idx];
|
||||||
|
if (!mo)
|
||||||
|
return Transform3d::Identity();
|
||||||
|
|
||||||
|
// 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 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;
|
||||||
|
|
||||||
|
return translation_transform(cut_center_offset) * m_rotation_m;
|
||||||
|
}
|
||||||
|
|
||||||
void GLGizmoCut3D::perform_cut(const Selection& selection)
|
void GLGizmoCut3D::perform_cut(const Selection& selection)
|
||||||
{
|
{
|
||||||
if (!can_perform_cut())
|
if (!can_perform_cut())
|
||||||
@ -2149,32 +2506,118 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
|
|||||||
// deactivate CutGizmo and than perform a cut
|
// deactivate CutGizmo and than perform a cut
|
||||||
m_parent.reset_all_gizmos();
|
m_parent.reset_all_gizmos();
|
||||||
|
|
||||||
// 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 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;
|
|
||||||
|
|
||||||
// perform cut
|
// perform cut
|
||||||
{
|
{
|
||||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by Plane"));
|
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by Plane"));
|
||||||
|
|
||||||
|
// This shall delete the part selection class and deallocate the memory.
|
||||||
|
ScopeGuard part_selection_killer([this]() { m_part_selection = PartSelection(); });
|
||||||
|
|
||||||
|
const bool cut_by_contour = m_part_selection.valid();
|
||||||
|
ModelObject* cut_mo = cut_by_contour ? m_part_selection.model_object() : nullptr;
|
||||||
|
if (cut_mo)
|
||||||
|
cut_mo->cut_connectors = mo->cut_connectors;
|
||||||
|
|
||||||
bool create_dowels_as_separate_object = false;
|
bool create_dowels_as_separate_object = false;
|
||||||
const bool has_connectors = !mo->cut_connectors.empty();
|
const bool has_connectors = !mo->cut_connectors.empty();
|
||||||
// update connectors pos as offset of its center before cut performing
|
// update connectors pos as offset of its center before cut performing
|
||||||
apply_connectors_in_model(mo, create_dowels_as_separate_object);
|
apply_connectors_in_model(cut_mo ? cut_mo : mo , create_dowels_as_separate_object);
|
||||||
|
|
||||||
plater->cut(object_idx, instance_idx, translation_transform(cut_center_offset) * m_rotation_m,
|
wxBusyCursor wait;
|
||||||
only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) |
|
|
||||||
only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) |
|
const Transform3d cut_matrix = get_cut_matrix(selection);
|
||||||
only_if(has_connectors ? false: m_keep_as_parts, ModelObjectCutAttribute::KeepAsParts) |
|
|
||||||
only_if(m_place_on_cut_upper, ModelObjectCutAttribute::PlaceOnCutUpper) |
|
ModelObjectCutAttributes attributes = only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) |
|
||||||
only_if(m_place_on_cut_lower, ModelObjectCutAttribute::PlaceOnCutLower) |
|
only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) |
|
||||||
only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) |
|
only_if(has_connectors ? false : m_keep_as_parts, ModelObjectCutAttribute::KeepAsParts) |
|
||||||
only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) |
|
only_if(m_place_on_cut_upper, ModelObjectCutAttribute::PlaceOnCutUpper) |
|
||||||
only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels) |
|
only_if(m_place_on_cut_lower, ModelObjectCutAttribute::PlaceOnCutLower) |
|
||||||
only_if(!has_connectors, ModelObjectCutAttribute::InvalidateCutInfo));
|
only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) |
|
||||||
|
only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) |
|
||||||
|
only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels) |
|
||||||
|
only_if(!has_connectors, ModelObjectCutAttribute::InvalidateCutInfo);
|
||||||
|
|
||||||
|
ModelObjectPtrs cut_object_ptrs;
|
||||||
|
if (cut_by_contour) {
|
||||||
|
// apply cut attributes for object
|
||||||
|
if (m_keep_upper && m_keep_lower)
|
||||||
|
cut_mo->apply_cut_attributes(ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper |
|
||||||
|
only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels));
|
||||||
|
|
||||||
|
// Clone the object to duplicate instances, materials etc.
|
||||||
|
ModelObject* upper{ nullptr };
|
||||||
|
if (m_keep_upper) cut_mo->clone_for_cut(&upper);
|
||||||
|
ModelObject* lower{ nullptr };
|
||||||
|
if (m_keep_lower) cut_mo->clone_for_cut(&lower);
|
||||||
|
|
||||||
|
auto add_cut_objects = [this, &instance_idx, &cut_matrix](ModelObjectPtrs& cut_objects, ModelObject* upper, ModelObject* lower, bool invalidate_cut = true) {
|
||||||
|
if (upper && !upper->volumes.empty()) {
|
||||||
|
ModelObject::reset_instance_transformation(upper, instance_idx, cut_matrix, m_place_on_cut_upper, m_rotate_upper);
|
||||||
|
if (invalidate_cut)
|
||||||
|
upper->invalidate_cut();
|
||||||
|
cut_objects.push_back(upper);
|
||||||
|
}
|
||||||
|
if (lower && !lower->volumes.empty()) {
|
||||||
|
ModelObject::reset_instance_transformation(lower, instance_idx, cut_matrix, m_place_on_cut_lower, m_place_on_cut_lower || m_rotate_lower);
|
||||||
|
if (invalidate_cut)
|
||||||
|
lower->invalidate_cut();
|
||||||
|
cut_objects.push_back(lower);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const size_t cut_parts_cnt = m_part_selection.parts().size();
|
||||||
|
for (size_t id = 0; id < cut_parts_cnt; ++id) {
|
||||||
|
if (ModelObject* obj = (m_part_selection.parts()[id].selected ? upper : lower))
|
||||||
|
obj->add_volume(*(cut_mo->volumes[id]));
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelVolumePtrs& volumes = cut_mo->volumes;
|
||||||
|
if (volumes.size() == cut_parts_cnt)
|
||||||
|
add_cut_objects(cut_object_ptrs, upper, lower);
|
||||||
|
else if (volumes.size() > cut_parts_cnt) {
|
||||||
|
for (size_t id = 0; id < cut_parts_cnt; id++)
|
||||||
|
delete *(volumes.begin() + id);
|
||||||
|
volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt);
|
||||||
|
|
||||||
|
const ModelObjectPtrs cut_connectors_obj = cut_mo->cut(instance_idx, get_cut_matrix(selection), attributes);
|
||||||
|
assert(create_dowels_as_separate_object ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2);
|
||||||
|
|
||||||
|
for (const ModelVolume* volume : cut_connectors_obj[0]->volumes)
|
||||||
|
upper->add_volume(*volume, volume->type());
|
||||||
|
for (const ModelVolume* volume : cut_connectors_obj[1]->volumes)
|
||||||
|
lower->add_volume(*volume, volume->type());
|
||||||
|
|
||||||
|
add_cut_objects(cut_object_ptrs, upper, lower, false);
|
||||||
|
|
||||||
|
if (cut_connectors_obj.size() >= 3)
|
||||||
|
for (size_t id = 2; id < cut_connectors_obj.size(); id++)
|
||||||
|
cut_object_ptrs.push_back(cut_connectors_obj[id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now merge all model parts together:
|
||||||
|
{
|
||||||
|
for (ModelObject* mo : cut_object_ptrs) {
|
||||||
|
TriangleMesh mesh;
|
||||||
|
for (const ModelVolume* mv : mo->volumes) {
|
||||||
|
if (mv->is_model_part()) {
|
||||||
|
TriangleMesh m = mv->mesh();
|
||||||
|
m.transform(mv->get_matrix());
|
||||||
|
mesh.merge(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! mesh.empty()) {
|
||||||
|
ModelVolume* new_volume = mo->add_volume(mesh);
|
||||||
|
for (int i=int(mo->volumes.size())-2; i>=0; --i)
|
||||||
|
if (mo->volumes[i]->type() == ModelVolumeType::MODEL_PART)
|
||||||
|
mo->delete_volume(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
cut_object_ptrs = mo->cut(instance_idx, cut_matrix, attributes);
|
||||||
|
|
||||||
|
plater->cut(object_idx, cut_object_ptrs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2182,7 +2625,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
|
|||||||
|
|
||||||
// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal
|
// 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.
|
// Return false if no intersection was found, true otherwise.
|
||||||
bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& pos, Vec3d& pos_world)
|
bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& pos, Vec3d& pos_world, bool respect_disabled_contour/* = true*/)
|
||||||
{
|
{
|
||||||
const float sla_shift = m_c->selection_info()->get_sla_shift();
|
const float sla_shift = m_c->selection_info()->get_sla_shift();
|
||||||
|
|
||||||
@ -2203,9 +2646,34 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& po
|
|||||||
hit = (point + t * direction);
|
hit = (point + t * direction);
|
||||||
} else
|
} else
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Now check if the hit is not obscured by a selected part on this side of the plane.
|
||||||
|
// FIXME: This would be better solved by remembering which contours are active. We will
|
||||||
|
// probably need that anyway because there is not other way to find out which contours
|
||||||
|
// to render. If you want to uncomment it, fix it first. It does not work yet.
|
||||||
|
/*for (size_t id = 0; id < m_part_selection.parts.size(); ++id) {
|
||||||
|
if (! m_part_selection.parts[id].selected) {
|
||||||
|
Vec3f pos, normal;
|
||||||
|
const ModelObject* model_object = m_part_selection.model_object;
|
||||||
|
const Vec3d volume_offset = m_part_selection.model_object->volumes[id]->get_offset();
|
||||||
|
Transform3d tr = model_object->instances[m_part_selection.instance_idx]->get_matrix() * model_object->volumes[id]->get_matrix();
|
||||||
|
if (m_part_selection.parts[id].raycaster.unproject_on_mesh(mouse_position, tr, camera, pos, normal))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
{
|
||||||
|
// Do not react to clicks outside a contour (or inside a contour that is ignored)
|
||||||
|
int cont_id = m_c->object_clipper()->is_projection_inside_cut(hit);
|
||||||
|
if (cont_id == -1)
|
||||||
|
return false;
|
||||||
|
if (m_part_selection.valid()) {
|
||||||
|
const std::vector<size_t>& ign = *m_part_selection.get_ignored_contours_ptr();
|
||||||
|
if (std::find(ign.begin(), ign.end(), cont_id) != ign.end())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (! m_c->object_clipper()->is_projection_inside_cut(hit))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// recalculate hit to object's local position
|
// recalculate hit to object's local position
|
||||||
Vec3d hit_d = hit;
|
Vec3d hit_d = hit;
|
||||||
@ -2230,6 +2698,7 @@ void GLGizmoCut3D::reset_connectors()
|
|||||||
m_c->selection_info()->model_object()->cut_connectors.clear();
|
m_c->selection_info()->model_object()->cut_connectors.clear();
|
||||||
update_raycasters_for_picking();
|
update_raycasters_for_picking();
|
||||||
clear_selection();
|
clear_selection();
|
||||||
|
check_and_update_connectors_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGizmoCut3D::init_connector_shapes()
|
void GLGizmoCut3D::init_connector_shapes()
|
||||||
@ -2284,6 +2753,8 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cut_line_processing()) {
|
if (cut_line_processing()) {
|
||||||
|
reset_cut_by_contours();
|
||||||
|
|
||||||
m_line_end = pt;
|
m_line_end = pt;
|
||||||
if (action == SLAGizmoEventType::LeftDown || action == SLAGizmoEventType::LeftUp) {
|
if (action == SLAGizmoEventType::LeftDown || action == SLAGizmoEventType::LeftUp) {
|
||||||
Vec3d line_dir = m_line_end - m_line_beg;
|
Vec3d line_dir = m_line_end - m_line_beg;
|
||||||
@ -2343,6 +2814,7 @@ bool GLGizmoCut3D::add_connector(CutConnectors& connectors, const Vec2d& mouse_p
|
|||||||
assert(m_selected.size() == connectors.size());
|
assert(m_selected.size() == connectors.size());
|
||||||
update_raycasters_for_picking();
|
update_raycasters_for_picking();
|
||||||
m_parent.set_as_dirty();
|
m_parent.set_as_dirty();
|
||||||
|
check_and_update_connectors_state();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -2368,6 +2840,7 @@ bool GLGizmoCut3D::delete_selected_connectors(CutConnectors& connectors)
|
|||||||
assert(m_selected.size() == connectors.size());
|
assert(m_selected.size() == connectors.size());
|
||||||
update_raycasters_for_picking();
|
update_raycasters_for_picking();
|
||||||
m_parent.set_as_dirty();
|
m_parent.set_as_dirty();
|
||||||
|
check_and_update_connectors_state();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2430,20 +2903,8 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi
|
|||||||
if (!m_keep_upper || !m_keep_lower)
|
if (!m_keep_upper || !m_keep_lower)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!m_connectors_editing) {
|
if (!m_connectors_editing)
|
||||||
if (0 && action == SLAGizmoEventType::LeftDown) {
|
|
||||||
// disable / enable current contour
|
|
||||||
Vec3d pos;
|
|
||||||
Vec3d pos_world;
|
|
||||||
if (unproject_on_cut_plane(mouse_position.cast<double>(), pos, pos_world)) {
|
|
||||||
// Following would inform the clipper about the mouse click, so it can
|
|
||||||
// toggle the respective contour as disabled.
|
|
||||||
m_c->object_clipper()->pass_mouse_click(pos_world);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors;
|
CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors;
|
||||||
|
|
||||||
|
@ -20,6 +20,8 @@ class Selection;
|
|||||||
|
|
||||||
enum class SLAGizmoEventType : unsigned char;
|
enum class SLAGizmoEventType : unsigned char;
|
||||||
|
|
||||||
|
namespace CommonGizmosDataObjects { class ObjectClipper; }
|
||||||
|
|
||||||
class GLGizmoCut3D : public GLGizmoBase
|
class GLGizmoCut3D : public GLGizmoBase
|
||||||
{
|
{
|
||||||
enum GrabberID {
|
enum GrabberID {
|
||||||
@ -133,8 +135,44 @@ class GLGizmoCut3D : public GLGizmoBase
|
|||||||
|
|
||||||
GLSelectionRectangle m_selection_rectangle;
|
GLSelectionRectangle m_selection_rectangle;
|
||||||
|
|
||||||
bool m_has_invalid_connector{ false };
|
std::vector<size_t> m_invalid_connectors_idxs;
|
||||||
bool m_was_cut_plane_dragged { false };
|
bool m_was_cut_plane_dragged { false };
|
||||||
|
bool m_was_contour_selected { false };
|
||||||
|
|
||||||
|
class PartSelection {
|
||||||
|
public:
|
||||||
|
PartSelection() = default;
|
||||||
|
PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc);
|
||||||
|
~PartSelection() { m_model.clear_objects(); }
|
||||||
|
|
||||||
|
struct Part {
|
||||||
|
GLModel glmodel;
|
||||||
|
MeshRaycaster raycaster;
|
||||||
|
bool selected;
|
||||||
|
};
|
||||||
|
|
||||||
|
void render(const Vec3d* normal, GLModel& sphere_model);
|
||||||
|
void toggle_selection(const Vec2d& mouse_pos);
|
||||||
|
void turn_over_selection();
|
||||||
|
ModelObject* model_object() { return m_model.objects.front(); }
|
||||||
|
bool valid() const { return m_valid; }
|
||||||
|
bool is_one_object() const;
|
||||||
|
const std::vector<Part>& parts() const { return m_parts; }
|
||||||
|
const std::vector<size_t>* get_ignored_contours_ptr() const { return (valid() ? &m_ignored_contours : nullptr); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Model m_model;
|
||||||
|
int m_instance_idx;
|
||||||
|
std::vector<Part> m_parts;
|
||||||
|
bool m_valid = false;
|
||||||
|
std::vector<std::pair<std::vector<size_t>, std::vector<size_t>>> m_contour_to_parts; // for each contour, there is a vector of parts above and a vector of parts below
|
||||||
|
std::vector<size_t> m_ignored_contours; // contour that should not be rendered (the parts on both sides will both be parts of the same object)
|
||||||
|
|
||||||
|
std::vector<Vec3d> m_contour_points; // Debugging
|
||||||
|
std::vector<std::vector<Vec3d>> m_debug_pts; // Debugging
|
||||||
|
};
|
||||||
|
|
||||||
|
PartSelection m_part_selection;
|
||||||
|
|
||||||
bool m_show_shortcuts{ false };
|
bool m_show_shortcuts{ false };
|
||||||
std::vector<std::pair<wxString, wxString>> m_shortcuts;
|
std::vector<std::pair<wxString, wxString>> m_shortcuts;
|
||||||
@ -176,7 +214,7 @@ public:
|
|||||||
GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||||
|
|
||||||
std::string get_tooltip() const override;
|
std::string get_tooltip() const override;
|
||||||
bool unproject_on_cut_plane(const Vec2d& mouse_pos, Vec3d& pos, Vec3d& pos_world);
|
bool unproject_on_cut_plane(const Vec2d& mouse_pos, Vec3d& pos, Vec3d& pos_world, bool respect_disabled_contour = true);
|
||||||
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
||||||
|
|
||||||
bool is_in_editing_mode() const override { return m_connectors_editing; }
|
bool is_in_editing_mode() const override { return m_connectors_editing; }
|
||||||
@ -229,6 +267,8 @@ protected:
|
|||||||
void reset_cut_plane();
|
void reset_cut_plane();
|
||||||
void set_connectors_editing(bool connectors_editing);
|
void set_connectors_editing(bool connectors_editing);
|
||||||
void flip_cut_plane();
|
void flip_cut_plane();
|
||||||
|
void process_contours();
|
||||||
|
void reset_cut_by_contours();
|
||||||
void render_flip_plane_button(bool disable_pred = false);
|
void render_flip_plane_button(bool disable_pred = false);
|
||||||
void add_vertical_scaled_interval(float interval);
|
void add_vertical_scaled_interval(float interval);
|
||||||
void add_horizontal_scaled_interval(float interval);
|
void add_horizontal_scaled_interval(float interval);
|
||||||
@ -257,6 +297,7 @@ protected:
|
|||||||
std::string get_action_snapshot_name() const override { return _u8L("Cut gizmo editing"); }
|
std::string get_action_snapshot_name() const override { return _u8L("Cut gizmo editing"); }
|
||||||
|
|
||||||
void data_changed(bool is_serializing) override;
|
void data_changed(bool is_serializing) override;
|
||||||
|
Transform3d get_cut_matrix(const Selection& selection);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void set_center(const Vec3d& center, bool update_tbb = false);
|
void set_center(const Vec3d& center, bool update_tbb = false);
|
||||||
@ -278,7 +319,7 @@ private:
|
|||||||
void discard_cut_line_processing();
|
void discard_cut_line_processing();
|
||||||
|
|
||||||
void render_cut_plane();
|
void render_cut_plane();
|
||||||
void render_model(GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix);
|
static void render_model(GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix);
|
||||||
void render_line(GLModel& line_model, const ColorRGBA& color, Transform3d view_model_matrix, float width);
|
void render_line(GLModel& line_model, const ColorRGBA& color, Transform3d view_model_matrix, float width);
|
||||||
void render_rotation_snapping(GrabberID axis, const ColorRGBA& color);
|
void render_rotation_snapping(GrabberID axis, const ColorRGBA& color);
|
||||||
void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix);
|
void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix);
|
||||||
@ -296,6 +337,7 @@ private:
|
|||||||
void update_connector_shape();
|
void update_connector_shape();
|
||||||
void validate_connector_settings();
|
void validate_connector_settings();
|
||||||
bool process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position);
|
bool process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position);
|
||||||
|
void check_and_update_connectors_state();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace GUI
|
} // namespace GUI
|
||||||
|
@ -370,27 +370,53 @@ void ObjectClipper::on_release()
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectClipper::render_cut() const
|
void ObjectClipper::render_cut(const std::vector<size_t>* ignore_idxs) const
|
||||||
{
|
{
|
||||||
if (m_clp_ratio == 0.)
|
if (m_clp_ratio == 0.)
|
||||||
return;
|
return;
|
||||||
const SelectionInfo* sel_info = get_pool()->selection_info();
|
const SelectionInfo* sel_info = get_pool()->selection_info();
|
||||||
const Geometry::Transformation inst_trafo = sel_info->model_object()->instances[sel_info->get_active_instance()]->get_transformation();
|
const Geometry::Transformation inst_trafo = sel_info->model_object()->instances[sel_info->get_active_instance()]->get_transformation();
|
||||||
|
|
||||||
|
std::vector<size_t> ignore_idxs_local = ignore_idxs ? *ignore_idxs : std::vector<size_t>();
|
||||||
|
|
||||||
for (auto& clipper : m_clippers) {
|
for (auto& clipper : m_clippers) {
|
||||||
Geometry::Transformation trafo = inst_trafo * clipper.second;
|
Geometry::Transformation trafo = inst_trafo * clipper.second;
|
||||||
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
|
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
|
||||||
clipper.first->set_plane(*m_clp);
|
clipper.first->set_plane(*m_clp);
|
||||||
clipper.first->set_transformation(trafo);
|
clipper.first->set_transformation(trafo);
|
||||||
clipper.first->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
|
clipper.first->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
|
||||||
clipper.first->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f });
|
clipper.first->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f }, &ignore_idxs_local);
|
||||||
clipper.first->render_contour({ 1.f, 1.f, 1.f, 1.f });
|
clipper.first->render_contour({ 1.f, 1.f, 1.f, 1.f }, &ignore_idxs_local);
|
||||||
|
|
||||||
|
// Now update the ignore idxs. Find the first element belonging to the next clipper,
|
||||||
|
// and remove everything before it and decrement everything by current number of contours.
|
||||||
|
const int num_of_contours = clipper.first->get_number_of_contours();
|
||||||
|
ignore_idxs_local.erase(ignore_idxs_local.begin(), std::find_if(ignore_idxs_local.begin(), ignore_idxs_local.end(), [num_of_contours](size_t idx) { return idx >= num_of_contours; } ));
|
||||||
|
for (size_t& idx : ignore_idxs_local)
|
||||||
|
idx -= num_of_contours;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ObjectClipper::is_projection_inside_cut(const Vec3d& point) const
|
|
||||||
|
int ObjectClipper::get_number_of_contours() const
|
||||||
{
|
{
|
||||||
return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [point](const auto& cl) { return cl.first->is_projection_inside_cut(point); });
|
int sum = 0;
|
||||||
|
for (const auto& [clipper, trafo] : m_clippers)
|
||||||
|
sum += clipper->get_number_of_contours();
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ObjectClipper::is_projection_inside_cut(const Vec3d& point) const
|
||||||
|
{
|
||||||
|
if (m_clp_ratio == 0.)
|
||||||
|
return -1;
|
||||||
|
int idx_offset = 0;
|
||||||
|
for (const auto& [clipper, trafo] : m_clippers) {
|
||||||
|
if (int idx = clipper->is_projection_inside_cut(point); idx != -1)
|
||||||
|
return idx_offset + idx;
|
||||||
|
idx_offset += clipper->get_number_of_contours();
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ObjectClipper::has_valid_contour() const
|
bool ObjectClipper::has_valid_contour() const
|
||||||
@ -398,6 +424,18 @@ 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.first->has_valid_contour(); });
|
return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [](const auto& cl) { return cl.first->has_valid_contour(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<Vec3d> ObjectClipper::point_per_contour() const
|
||||||
|
{
|
||||||
|
std::vector<Vec3d> pts;
|
||||||
|
|
||||||
|
for (const auto& clipper : m_clippers) {
|
||||||
|
const std::vector<Vec3d> pts_clipper = clipper.first->point_per_contour();
|
||||||
|
pts.insert(pts.end(), pts_clipper.begin(), pts_clipper.end());;
|
||||||
|
}
|
||||||
|
return pts;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal)
|
void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal)
|
||||||
{
|
{
|
||||||
const ModelObject* mo = get_pool()->selection_info()->model_object();
|
const ModelObject* mo = get_pool()->selection_info()->model_object();
|
||||||
@ -436,16 +474,6 @@ void ObjectClipper::set_behavior(bool hide_clipped, bool fill_cut, double contou
|
|||||||
clipper.first->set_behaviour(fill_cut, contour_width);
|
clipper.first->set_behaviour(fill_cut, contour_width);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectClipper::pass_mouse_click(const Vec3d& pt)
|
|
||||||
{
|
|
||||||
for (auto& clipper : m_clippers)
|
|
||||||
clipper.first->pass_mouse_click(pt);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Vec3d> ObjectClipper::get_disabled_contours() const
|
|
||||||
{
|
|
||||||
return std::vector<Vec3d>();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SupportsClipper::on_update()
|
void SupportsClipper::on_update()
|
||||||
{
|
{
|
||||||
|
@ -242,15 +242,15 @@ public:
|
|||||||
void set_normal(const Vec3d& dir);
|
void set_normal(const Vec3d& dir);
|
||||||
double get_position() const { return m_clp_ratio; }
|
double get_position() const { return m_clp_ratio; }
|
||||||
const ClippingPlane* get_clipping_plane(bool ignore_hide_clipped = false) const;
|
const ClippingPlane* get_clipping_plane(bool ignore_hide_clipped = false) const;
|
||||||
void render_cut() const;
|
void render_cut(const std::vector<size_t>* ignore_idxs = nullptr) const;
|
||||||
void set_position_by_ratio(double pos, bool keep_normal);
|
void set_position_by_ratio(double pos, bool keep_normal);
|
||||||
void set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos);
|
void set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos);
|
||||||
void set_behavior(bool hide_clipped, bool fill_cut, double contour_width);
|
void set_behavior(bool hide_clipped, bool fill_cut, double contour_width);
|
||||||
|
|
||||||
void pass_mouse_click(const Vec3d& pt);
|
int get_number_of_contours() const;
|
||||||
std::vector<Vec3d> get_disabled_contours() const;
|
std::vector<Vec3d> point_per_contour() const;
|
||||||
|
|
||||||
bool is_projection_inside_cut(const Vec3d& point_in) const;
|
int is_projection_inside_cut(const Vec3d& point_in) const;
|
||||||
bool has_valid_contour() const;
|
bool has_valid_contour() const;
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ void MeshClipper::set_transformation(const Geometry::Transformation& trafo)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeshClipper::render_cut(const ColorRGBA& color)
|
void MeshClipper::render_cut(const ColorRGBA& color, const std::vector<size_t>* ignore_idxs)
|
||||||
{
|
{
|
||||||
if (! m_result)
|
if (! m_result)
|
||||||
recalculate_triangles();
|
recalculate_triangles();
|
||||||
@ -108,7 +108,10 @@ void MeshClipper::render_cut(const ColorRGBA& color)
|
|||||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||||
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
|
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
|
||||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||||
for (CutIsland& isl : m_result->cut_islands) {
|
for (size_t i=0; i<m_result->cut_islands.size(); ++i) {
|
||||||
|
if (ignore_idxs && std::binary_search(ignore_idxs->begin(), ignore_idxs->end(), i))
|
||||||
|
continue;
|
||||||
|
CutIsland& isl = m_result->cut_islands[i];
|
||||||
isl.model.set_color(isl.disabled ? ColorRGBA(0.5f, 0.5f, 0.5f, 1.f) : color);
|
isl.model.set_color(isl.disabled ? ColorRGBA(0.5f, 0.5f, 0.5f, 1.f) : color);
|
||||||
isl.model.render();
|
isl.model.render();
|
||||||
}
|
}
|
||||||
@ -120,7 +123,7 @@ void MeshClipper::render_cut(const ColorRGBA& color)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void MeshClipper::render_contour(const ColorRGBA& color)
|
void MeshClipper::render_contour(const ColorRGBA& color, const std::vector<size_t>* ignore_idxs)
|
||||||
{
|
{
|
||||||
if (! m_result)
|
if (! m_result)
|
||||||
recalculate_triangles();
|
recalculate_triangles();
|
||||||
@ -135,7 +138,10 @@ void MeshClipper::render_contour(const ColorRGBA& color)
|
|||||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||||
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
|
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
|
||||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||||
for (CutIsland& isl : m_result->cut_islands) {
|
for (size_t i=0; i<m_result->cut_islands.size(); ++i) {
|
||||||
|
if (ignore_idxs && std::binary_search(ignore_idxs->begin(), ignore_idxs->end(), i))
|
||||||
|
continue;
|
||||||
|
CutIsland& isl = m_result->cut_islands[i];
|
||||||
isl.model_expanded.set_color(isl.disabled ? ColorRGBA(1.f, 0.f, 0.f, 1.f) : color);
|
isl.model_expanded.set_color(isl.disabled ? ColorRGBA(1.f, 0.f, 0.f, 1.f) : color);
|
||||||
isl.model_expanded.render();
|
isl.model_expanded.render();
|
||||||
}
|
}
|
||||||
@ -146,18 +152,19 @@ void MeshClipper::render_contour(const ColorRGBA& color)
|
|||||||
curr_shader->start_using();
|
curr_shader->start_using();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MeshClipper::is_projection_inside_cut(const Vec3d& point_in) const
|
int MeshClipper::is_projection_inside_cut(const Vec3d& point_in) const
|
||||||
{
|
{
|
||||||
if (!m_result || m_result->cut_islands.empty())
|
if (!m_result || m_result->cut_islands.empty())
|
||||||
return false;
|
return -1;
|
||||||
Vec3d point = m_result->trafo.inverse() * point_in;
|
Vec3d point = m_result->trafo.inverse() * point_in;
|
||||||
Point pt_2d = Point::new_scale(Vec2d(point.x(), point.y()));
|
Point pt_2d = Point::new_scale(Vec2d(point.x(), point.y()));
|
||||||
|
|
||||||
for (const CutIsland& isl : m_result->cut_islands) {
|
for (int i=0; i<int(m_result->cut_islands.size()); ++i) {
|
||||||
|
const CutIsland& isl = m_result->cut_islands[i];
|
||||||
if (isl.expoly_bb.contains(pt_2d) && isl.expoly.contains(pt_2d))
|
if (isl.expoly_bb.contains(pt_2d) && isl.expoly.contains(pt_2d))
|
||||||
return !isl.disabled;
|
return i; // TODO: handle intersecting contours
|
||||||
}
|
}
|
||||||
return false;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MeshClipper::has_valid_contour() const
|
bool MeshClipper::has_valid_contour() const
|
||||||
@ -165,20 +172,48 @@ 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(); });
|
return m_result && std::any_of(m_result->cut_islands.begin(), m_result->cut_islands.end(), [](const CutIsland& isl) { return !isl.expoly.empty(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<Vec3d> MeshClipper::point_per_contour() const
|
||||||
void MeshClipper::pass_mouse_click(const Vec3d& point_in)
|
|
||||||
{
|
{
|
||||||
if (! m_result || m_result->cut_islands.empty())
|
assert(m_result);
|
||||||
return;
|
std::vector<Vec3d> out;
|
||||||
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) {
|
||||||
|
assert(isl.expoly.contour.size() > 2);
|
||||||
for (CutIsland& isl : m_result->cut_islands) {
|
// Now return a point lying inside the contour but not in a hole.
|
||||||
if (isl.expoly_bb.contains(pt_2d) && isl.expoly.contains(pt_2d))
|
// We do this by taking a point lying close to the edge, repeating
|
||||||
isl.disabled = ! isl.disabled;
|
// this several times for different edges and distances from them.
|
||||||
|
// (We prefer point not extremely close to the border.
|
||||||
|
bool done = false;
|
||||||
|
Vec2d p;
|
||||||
|
size_t i = 1;
|
||||||
|
while (i < isl.expoly.contour.size()) {
|
||||||
|
const Vec2d& a = unscale(isl.expoly.contour.points[i-1]);
|
||||||
|
const Vec2d& b = unscale(isl.expoly.contour.points[i]);
|
||||||
|
Vec2d n = (b-a).normalized();
|
||||||
|
std::swap(n.x(), n.y());
|
||||||
|
n.x() = -1 * n.x();
|
||||||
|
double f = 10.;
|
||||||
|
while (f > 0.05) {
|
||||||
|
p = (0.5*(b+a)) + f * n;
|
||||||
|
if (isl.expoly.contains(Point::new_scale(p))) {
|
||||||
|
done = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
f = f/10.;
|
||||||
|
}
|
||||||
|
if (done)
|
||||||
|
break;
|
||||||
|
i += std::max(size_t(2), isl.expoly.contour.size() / 5);
|
||||||
|
}
|
||||||
|
// If the above failed, just return the centroid, regardless of whether
|
||||||
|
// it is inside the contour or in a hole (we must return something).
|
||||||
|
Vec2d c = done ? p : unscale(isl.expoly.contour.centroid());
|
||||||
|
out.emplace_back(m_result->trafo * Vec3d(c.x(), c.y(), 0.));
|
||||||
}
|
}
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void MeshClipper::recalculate_triangles()
|
void MeshClipper::recalculate_triangles()
|
||||||
{
|
{
|
||||||
m_result = ClipResult();
|
m_result = ClipResult();
|
||||||
@ -357,8 +392,18 @@ void MeshClipper::recalculate_triangles()
|
|||||||
}
|
}
|
||||||
|
|
||||||
isl.expoly = std::move(exp);
|
isl.expoly = std::move(exp);
|
||||||
isl.expoly_bb = get_extents(exp);
|
isl.expoly_bb = get_extents(isl.expoly);
|
||||||
|
|
||||||
|
Point centroid_scaled = isl.expoly.contour.centroid();
|
||||||
|
Vec3d centroid_world = m_result->trafo * Vec3d(unscale(centroid_scaled).x(), unscale(centroid_scaled).y(), 0.);
|
||||||
|
isl.hash = isl.expoly.contour.size() + size_t(std::abs(100.*centroid_world.x())) + size_t(std::abs(100.*centroid_world.y())) + size_t(std::abs(100.*centroid_world.z()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now sort the islands so they are in defined order. This is a hack needed by cut gizmo, which sometimes
|
||||||
|
// flips the normal of the cut, in which case the contours stay the same but their order may change.
|
||||||
|
std::sort(m_result->cut_islands.begin(), m_result->cut_islands.end(), [](const CutIsland& a, const CutIsland& b) {
|
||||||
|
return a.hash < b.hash;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -115,13 +115,14 @@ public:
|
|||||||
|
|
||||||
// Render the triangulated cut. Transformation matrices should
|
// Render the triangulated cut. Transformation matrices should
|
||||||
// be set in world coords.
|
// be set in world coords.
|
||||||
void render_cut(const ColorRGBA& color);
|
void render_cut(const ColorRGBA& color, const std::vector<size_t>* ignore_idxs = nullptr);
|
||||||
void render_contour(const ColorRGBA& color);
|
void render_contour(const ColorRGBA& color, const std::vector<size_t>* ignore_idxs = nullptr);
|
||||||
|
|
||||||
void pass_mouse_click(const Vec3d& pt);
|
// Returns index of the contour which was clicked, -1 otherwise.
|
||||||
|
int is_projection_inside_cut(const Vec3d& point) const;
|
||||||
bool is_projection_inside_cut(const Vec3d& point) const;
|
|
||||||
bool has_valid_contour() const;
|
bool has_valid_contour() const;
|
||||||
|
int get_number_of_contours() const { return m_result ? m_result->cut_islands.size() : 0; }
|
||||||
|
std::vector<Vec3d> point_per_contour() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void recalculate_triangles();
|
void recalculate_triangles();
|
||||||
@ -140,6 +141,7 @@ private:
|
|||||||
ExPolygon expoly;
|
ExPolygon expoly;
|
||||||
BoundingBox expoly_bb;
|
BoundingBox expoly_bb;
|
||||||
bool disabled = false;
|
bool disabled = false;
|
||||||
|
size_t hash;
|
||||||
};
|
};
|
||||||
struct ClipResult {
|
struct ClipResult {
|
||||||
std::vector<CutIsland> cut_islands;
|
std::vector<CutIsland> cut_islands;
|
||||||
|
@ -6266,7 +6266,11 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_mat
|
|||||||
wxBusyCursor wait;
|
wxBusyCursor wait;
|
||||||
|
|
||||||
const auto new_objects = object->cut(instance_idx, cut_matrix, attributes);
|
const auto new_objects = object->cut(instance_idx, cut_matrix, attributes);
|
||||||
|
cut(obj_idx, new_objects);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Plater::cut(size_t obj_idx, const ModelObjectPtrs& new_objects)
|
||||||
|
{
|
||||||
model().delete_object(obj_idx);
|
model().delete_object(obj_idx);
|
||||||
sidebar().obj_list()->delete_object_from_list(obj_idx);
|
sidebar().obj_list()->delete_object_from_list(obj_idx);
|
||||||
|
|
||||||
@ -6284,6 +6288,8 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_mat
|
|||||||
size_t last_id = p->model.objects.size() - 1;
|
size_t last_id = p->model.objects.size() - 1;
|
||||||
for (size_t i = 0; i < new_objects.size(); ++i)
|
for (size_t i = 0; i < new_objects.size(); ++i)
|
||||||
selection.add_object((unsigned int)(last_id - i), i == 0);
|
selection.add_object((unsigned int)(last_id - i), i == 0);
|
||||||
|
|
||||||
|
arrange();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Plater::export_gcode(bool prefer_removable)
|
void Plater::export_gcode(bool prefer_removable)
|
||||||
|
@ -260,6 +260,7 @@ public:
|
|||||||
void toggle_layers_editing(bool enable);
|
void toggle_layers_editing(bool enable);
|
||||||
|
|
||||||
void cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes);
|
void cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes);
|
||||||
|
void cut(size_t init_obj_idx, const ModelObjectPtrs& cut_objects);
|
||||||
|
|
||||||
void export_gcode(bool prefer_removable);
|
void export_gcode(bool prefer_removable);
|
||||||
void export_stl_obj(bool extended = false, bool selection_only = false);
|
void export_stl_obj(bool extended = false, bool selection_only = false);
|
||||||
|
Loading…
Reference in New Issue
Block a user