Merge remote-tracking branch 'origin/dev' into ys_manipulation_panel_rw
This commit is contained in:
commit
e34ef1dd07
13 changed files with 415 additions and 199 deletions
|
@ -151,8 +151,8 @@ bool stl_write_binary(stl_file *stl, const char *file, const char *label)
|
||||||
memcpy(buffer, &stl->stats.number_of_facets, 4);
|
memcpy(buffer, &stl->stats.number_of_facets, 4);
|
||||||
stl_internal_reverse_quads(buffer, 4);
|
stl_internal_reverse_quads(buffer, 4);
|
||||||
fwrite(buffer, 4, 1, fp);
|
fwrite(buffer, 4, 1, fp);
|
||||||
for (size_t i = 0; i < stl->stats.number_of_facets; ++ i) {
|
for (const stl_facet &facet : stl->facet_start) {
|
||||||
memcpy(buffer, stl->facet_start + i, 50);
|
memcpy(buffer, &facet, 50);
|
||||||
// Convert to little endian.
|
// Convert to little endian.
|
||||||
stl_internal_reverse_quads(buffer, 48);
|
stl_internal_reverse_quads(buffer, 48);
|
||||||
fwrite(buffer, SIZEOF_STL_FACET, 1, fp);
|
fwrite(buffer, SIZEOF_STL_FACET, 1, fp);
|
||||||
|
|
|
@ -175,20 +175,20 @@ size_t ExtrusionEntityCollection::items_count() const
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a single vector of pointers to all non-collection items contained in this one.
|
// Returns a single vector of pointers to all non-collection items contained in this one.
|
||||||
void ExtrusionEntityCollection::flatten(ExtrusionEntityCollection* retval) const
|
|
||||||
{
|
|
||||||
for (const ExtrusionEntity *entity : this->entities)
|
|
||||||
if (entity->is_collection())
|
|
||||||
retval->append(static_cast<const ExtrusionEntityCollection*>(entity)->flatten().entities);
|
|
||||||
else
|
|
||||||
retval->append(*entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
ExtrusionEntityCollection ExtrusionEntityCollection::flatten() const
|
ExtrusionEntityCollection ExtrusionEntityCollection::flatten() const
|
||||||
{
|
{
|
||||||
ExtrusionEntityCollection coll;
|
struct Flatten {
|
||||||
this->flatten(&coll);
|
ExtrusionEntityCollection out;
|
||||||
return coll;
|
void recursive_do(const ExtrusionEntityCollection &collection) {
|
||||||
|
for (const ExtrusionEntity* entity : collection.entities)
|
||||||
|
if (entity->is_collection())
|
||||||
|
this->recursive_do(*static_cast<const ExtrusionEntityCollection*>(entity));
|
||||||
|
else
|
||||||
|
out.append(*entity);
|
||||||
|
}
|
||||||
|
} flatten;
|
||||||
|
flatten.recursive_do(*this);
|
||||||
|
return flatten.out;
|
||||||
}
|
}
|
||||||
|
|
||||||
double ExtrusionEntityCollection::min_mm3_per_mm() const
|
double ExtrusionEntityCollection::min_mm3_per_mm() const
|
||||||
|
|
|
@ -85,7 +85,6 @@ public:
|
||||||
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
|
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
|
||||||
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
|
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
|
||||||
size_t items_count() const;
|
size_t items_count() const;
|
||||||
void flatten(ExtrusionEntityCollection* retval) const;
|
|
||||||
ExtrusionEntityCollection flatten() const;
|
ExtrusionEntityCollection flatten() const;
|
||||||
double min_mm3_per_mm() const;
|
double min_mm3_per_mm() const;
|
||||||
double total_volume() const override { double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; }
|
double total_volume() const override { double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; }
|
||||||
|
|
|
@ -285,6 +285,9 @@ LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent,
|
||||||
{
|
{
|
||||||
this->SetFont(wxGetApp().normal_font());
|
this->SetFont(wxGetApp().normal_font());
|
||||||
|
|
||||||
|
// Reset m_enter_pressed flag to _false_, when value is editing
|
||||||
|
this->Bind(wxEVT_TEXT, [this](wxEvent&) { m_enter_pressed = false; }, this->GetId());
|
||||||
|
|
||||||
this->Bind(wxEVT_TEXT_ENTER, [this, edit_fn](wxEvent&)
|
this->Bind(wxEVT_TEXT_ENTER, [this, edit_fn](wxEvent&)
|
||||||
{
|
{
|
||||||
m_enter_pressed = true;
|
m_enter_pressed = true;
|
||||||
|
@ -307,7 +310,7 @@ LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent,
|
||||||
if (!m_enter_pressed) {
|
if (!m_enter_pressed) {
|
||||||
#ifndef __WXGTK__
|
#ifndef __WXGTK__
|
||||||
/* Update data for next editor selection.
|
/* Update data for next editor selection.
|
||||||
* But under GTK it lucks like there is no information about selected control at e.GetWindow(),
|
* But under GTK it looks like there is no information about selected control at e.GetWindow(),
|
||||||
* so we'll take it from wxEVT_LEFT_DOWN event
|
* so we'll take it from wxEVT_LEFT_DOWN event
|
||||||
* */
|
* */
|
||||||
LayerRangeEditor* new_editor = dynamic_cast<LayerRangeEditor*>(e.GetWindow());
|
LayerRangeEditor* new_editor = dynamic_cast<LayerRangeEditor*>(e.GetWindow());
|
||||||
|
|
|
@ -279,6 +279,7 @@ void ObjectList::create_popup_menus()
|
||||||
create_part_popupmenu(&m_menu_part);
|
create_part_popupmenu(&m_menu_part);
|
||||||
create_sla_object_popupmenu(&m_menu_sla_object);
|
create_sla_object_popupmenu(&m_menu_sla_object);
|
||||||
create_instance_popupmenu(&m_menu_instance);
|
create_instance_popupmenu(&m_menu_instance);
|
||||||
|
create_default_popupmenu(&m_menu_default);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectList::get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxDataViewItem& input_item/* = wxDataViewItem(nullptr)*/)
|
void ObjectList::get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxDataViewItem& input_item/* = wxDataViewItem(nullptr)*/)
|
||||||
|
@ -783,26 +784,42 @@ void ObjectList::OnChar(wxKeyEvent& event)
|
||||||
|
|
||||||
void ObjectList::OnContextMenu(wxDataViewEvent&)
|
void ObjectList::OnContextMenu(wxDataViewEvent&)
|
||||||
{
|
{
|
||||||
list_manipulation();
|
list_manipulation(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectList::list_manipulation()
|
void ObjectList::list_manipulation(bool evt_context_menu/* = false*/)
|
||||||
{
|
{
|
||||||
wxDataViewItem item;
|
wxDataViewItem item;
|
||||||
wxDataViewColumn* col = nullptr;
|
wxDataViewColumn* col = nullptr;
|
||||||
const wxPoint pt = get_mouse_position_in_control();
|
const wxPoint pt = get_mouse_position_in_control();
|
||||||
HitTest(pt, item, col);
|
HitTest(pt, item, col);
|
||||||
|
|
||||||
if (!item || col == nullptr) {
|
/* Note: Under OSX right click doesn't send "selection changed" event.
|
||||||
|
* It means that Selection() will be return still previously selected item.
|
||||||
|
* Thus under OSX we should force UnselectAll(), when item and col are nullptr,
|
||||||
|
* and select new item otherwise.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
if (wxOSX && col == nullptr)
|
||||||
|
UnselectAll();
|
||||||
|
if (evt_context_menu) {
|
||||||
|
show_context_menu(evt_context_menu);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wxOSX && item && col) {
|
||||||
|
UnselectAll();
|
||||||
|
Select(item);
|
||||||
|
}
|
||||||
|
|
||||||
const wxString title = col->GetTitle();
|
const wxString title = col->GetTitle();
|
||||||
|
|
||||||
if (title == " ")
|
if (title == " ")
|
||||||
toggle_printable_state(item);
|
toggle_printable_state(item);
|
||||||
else if (title == _("Editing"))
|
else if (title == _("Editing"))
|
||||||
show_context_menu();
|
show_context_menu(evt_context_menu);
|
||||||
else if (title == _("Name"))
|
else if (title == _("Name"))
|
||||||
{
|
{
|
||||||
int obj_idx, vol_idx;
|
int obj_idx, vol_idx;
|
||||||
|
@ -818,7 +835,7 @@ void ObjectList::list_manipulation()
|
||||||
#endif //__WXMSW__
|
#endif //__WXMSW__
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectList::show_context_menu()
|
void ObjectList::show_context_menu(const bool evt_context_menu)
|
||||||
{
|
{
|
||||||
if (multiple_selection())
|
if (multiple_selection())
|
||||||
{
|
{
|
||||||
|
@ -831,22 +848,26 @@ void ObjectList::show_context_menu()
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto item = GetSelection();
|
const auto item = GetSelection();
|
||||||
|
wxMenu* menu {nullptr};
|
||||||
if (item)
|
if (item)
|
||||||
{
|
{
|
||||||
const ItemType type = m_objects_model->GetItemType(item);
|
const ItemType type = m_objects_model->GetItemType(item);
|
||||||
if (!(type & (itObject | itVolume | itLayer | itInstance)))
|
if (!(type & (itObject | itVolume | itLayer | itInstance)))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
wxMenu* menu = type & itInstance ? &m_menu_instance :
|
menu = type & itInstance ? &m_menu_instance :
|
||||||
type & itLayer ? &m_menu_layer :
|
type & itLayer ? &m_menu_layer :
|
||||||
m_objects_model->GetParent(item) != wxDataViewItem(nullptr) ? &m_menu_part :
|
m_objects_model->GetParent(item) != wxDataViewItem(nullptr) ? &m_menu_part :
|
||||||
printer_technology() == ptFFF ? &m_menu_object : &m_menu_sla_object;
|
printer_technology() == ptFFF ? &m_menu_object : &m_menu_sla_object;
|
||||||
|
|
||||||
if (!(type & itInstance))
|
if (!(type & itInstance))
|
||||||
append_menu_item_settings(menu);
|
append_menu_item_settings(menu);
|
||||||
|
|
||||||
wxGetApp().plater()->PopupMenu(menu);
|
|
||||||
}
|
}
|
||||||
|
else if (evt_context_menu)
|
||||||
|
menu = &m_menu_default;
|
||||||
|
|
||||||
|
if (menu)
|
||||||
|
wxGetApp().plater()->PopupMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectList::copy()
|
void ObjectList::copy()
|
||||||
|
@ -1286,13 +1307,16 @@ void ObjectList::show_settings(const wxDataViewItem settings_item)
|
||||||
wxMenu* ObjectList::append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type) {
|
wxMenu* ObjectList::append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type) {
|
||||||
auto sub_menu = new wxMenu;
|
auto sub_menu = new wxMenu;
|
||||||
|
|
||||||
if (wxGetApp().get_mode() == comExpert) {
|
if (wxGetApp().get_mode() == comExpert && type != ModelVolumeType::INVALID) {
|
||||||
append_menu_item(sub_menu, wxID_ANY, _(L("Load")) + " " + dots, "",
|
append_menu_item(sub_menu, wxID_ANY, _(L("Load")) + " " + dots, "",
|
||||||
[this, type](wxCommandEvent&) { load_subobject(type); }, "", menu);
|
[this, type](wxCommandEvent&) { load_subobject(type); }, "", menu);
|
||||||
sub_menu->AppendSeparator();
|
sub_menu->AppendSeparator();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& item : { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") }) {
|
for (auto& item : { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") })
|
||||||
|
{
|
||||||
|
if (type == ModelVolumeType::INVALID && item == "Slab")
|
||||||
|
continue;
|
||||||
append_menu_item(sub_menu, wxID_ANY, _(item), "",
|
append_menu_item(sub_menu, wxID_ANY, _(item), "",
|
||||||
[this, type, item](wxCommandEvent&) { load_generic_subobject(item, type); }, "", menu);
|
[this, type, item](wxCommandEvent&) { load_generic_subobject(item, type); }, "", menu);
|
||||||
}
|
}
|
||||||
|
@ -1579,6 +1603,12 @@ void ObjectList::create_instance_popupmenu(wxMenu*menu)
|
||||||
}, m_menu_item_split_instances->GetId());
|
}, m_menu_item_split_instances->GetId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ObjectList::create_default_popupmenu(wxMenu*menu)
|
||||||
|
{
|
||||||
|
wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType::INVALID);
|
||||||
|
append_submenu(menu, sub_menu, wxID_ANY, _(L("Add Shape")), "", "add_part");
|
||||||
|
}
|
||||||
|
|
||||||
wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu)
|
wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu)
|
||||||
{
|
{
|
||||||
wxMenu *menu = new wxMenu;
|
wxMenu *menu = new wxMenu;
|
||||||
|
@ -1667,6 +1697,10 @@ void ObjectList::load_subobject(ModelVolumeType type)
|
||||||
|
|
||||||
if (sel_item)
|
if (sel_item)
|
||||||
select_item(sel_item);
|
select_item(sel_item);
|
||||||
|
|
||||||
|
#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
|
||||||
|
selection_changed();
|
||||||
|
#endif //no __WXOSX__ //__WXMSW__
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectList::load_part( ModelObject* model_object,
|
void ObjectList::load_part( ModelObject* model_object,
|
||||||
|
@ -1713,8 +1747,38 @@ void ObjectList::load_part( ModelObject* model_object,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static TriangleMesh create_mesh(const std::string& type_name, const BoundingBoxf3& bb)
|
||||||
|
{
|
||||||
|
TriangleMesh mesh;
|
||||||
|
|
||||||
|
const double side = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.1);
|
||||||
|
|
||||||
|
if (type_name == "Box")
|
||||||
|
// Sitting on the print bed, left front front corner at (0, 0).
|
||||||
|
mesh = make_cube(side, side, side);
|
||||||
|
else if (type_name == "Cylinder")
|
||||||
|
// Centered around 0, sitting on the print bed.
|
||||||
|
// The cylinder has the same volume as the box above.
|
||||||
|
mesh = make_cylinder(0.564 * side, side);
|
||||||
|
else if (type_name == "Sphere")
|
||||||
|
// Centered around 0, half the sphere below the print bed, half above.
|
||||||
|
// The sphere has the same volume as the box above.
|
||||||
|
mesh = make_sphere(0.62 * side, PI / 18);
|
||||||
|
else if (type_name == "Slab")
|
||||||
|
// Sitting on the print bed, left front front corner at (0, 0).
|
||||||
|
mesh = make_cube(bb.size().x() * 1.5, bb.size().y() * 1.5, bb.size().z() * 0.5);
|
||||||
|
mesh.repair();
|
||||||
|
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
void ObjectList::load_generic_subobject(const std::string& type_name, const ModelVolumeType type)
|
void ObjectList::load_generic_subobject(const std::string& type_name, const ModelVolumeType type)
|
||||||
{
|
{
|
||||||
|
if (type == ModelVolumeType::INVALID) {
|
||||||
|
load_shape_object(type_name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const int obj_idx = get_selected_obj_idx();
|
const int obj_idx = get_selected_obj_idx();
|
||||||
if (obj_idx < 0)
|
if (obj_idx < 0)
|
||||||
return;
|
return;
|
||||||
|
@ -1737,26 +1801,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
|
||||||
// Bounding box of the selected instance in world coordinate system including the translation, without modifiers.
|
// Bounding box of the selected instance in world coordinate system including the translation, without modifiers.
|
||||||
BoundingBoxf3 instance_bb = model_object.instance_bounding_box(instance_idx);
|
BoundingBoxf3 instance_bb = model_object.instance_bounding_box(instance_idx);
|
||||||
|
|
||||||
const wxString name = _(L("Generic")) + "-" + _(type_name);
|
TriangleMesh mesh = create_mesh(type_name, instance_bb);
|
||||||
TriangleMesh mesh;
|
|
||||||
|
|
||||||
double side = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.1);
|
|
||||||
|
|
||||||
if (type_name == "Box")
|
|
||||||
// Sitting on the print bed, left front front corner at (0, 0).
|
|
||||||
mesh = make_cube(side, side, side);
|
|
||||||
else if (type_name == "Cylinder")
|
|
||||||
// Centered around 0, sitting on the print bed.
|
|
||||||
// The cylinder has the same volume as the box above.
|
|
||||||
mesh = make_cylinder(0.564 * side, side);
|
|
||||||
else if (type_name == "Sphere")
|
|
||||||
// Centered around 0, half the sphere below the print bed, half above.
|
|
||||||
// The sphere has the same volume as the box above.
|
|
||||||
mesh = make_sphere(0.62 * side, PI / 18);
|
|
||||||
else if (type_name == "Slab")
|
|
||||||
// Sitting on the print bed, left front front corner at (0, 0).
|
|
||||||
mesh = make_cube(instance_bb.size().x()*1.5, instance_bb.size().y()*1.5, instance_bb.size().z()*0.5);
|
|
||||||
mesh.repair();
|
|
||||||
|
|
||||||
// Mesh will be centered when loading.
|
// Mesh will be centered when loading.
|
||||||
ModelVolume *new_volume = model_object.add_volume(std::move(mesh));
|
ModelVolume *new_volume = model_object.add_volume(std::move(mesh));
|
||||||
|
@ -1778,6 +1823,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
|
||||||
new_volume->set_offset(v->get_instance_transformation().get_matrix(true).inverse() * offset);
|
new_volume->set_offset(v->get_instance_transformation().get_matrix(true).inverse() * offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const wxString name = _(L("Generic")) + "-" + _(type_name);
|
||||||
new_volume->name = into_u8(name);
|
new_volume->name = into_u8(name);
|
||||||
// set a default extruder value, since user can't add it manually
|
// set a default extruder value, since user can't add it manually
|
||||||
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
|
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
|
||||||
|
@ -1795,6 +1841,57 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
|
||||||
#endif //no __WXOSX__ //__WXMSW__
|
#endif //no __WXOSX__ //__WXMSW__
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ObjectList::load_shape_object(const std::string& type_name)
|
||||||
|
{
|
||||||
|
const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
|
||||||
|
assert(selection.get_object_idx() == -1); // Add nothing is something is selected on 3DScene
|
||||||
|
if (selection.get_object_idx() != -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const int obj_idx = m_objects->size();
|
||||||
|
if (obj_idx < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
take_snapshot(_(L("Add Shape")));
|
||||||
|
|
||||||
|
// Create mesh
|
||||||
|
BoundingBoxf3 bb;
|
||||||
|
TriangleMesh mesh = create_mesh(type_name, bb);
|
||||||
|
|
||||||
|
// Add mesh to model as a new object
|
||||||
|
Model& model = wxGetApp().plater()->model();
|
||||||
|
const wxString name = _(L("Shape")) + "-" + _(type_name);
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
check_model_ids_validity(model);
|
||||||
|
#endif /* _DEBUG */
|
||||||
|
|
||||||
|
std::vector<size_t> object_idxs;
|
||||||
|
ModelObject* new_object = model.add_object();
|
||||||
|
new_object->name = into_u8(name);
|
||||||
|
new_object->add_instance(); // each object should have at list one instance
|
||||||
|
|
||||||
|
ModelVolume* new_volume = new_object->add_volume(mesh);
|
||||||
|
new_volume->name = into_u8(name);
|
||||||
|
// set a default extruder value, since user can't add it manually
|
||||||
|
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
|
||||||
|
new_object->invalidate_bounding_box();
|
||||||
|
|
||||||
|
const BoundingBoxf bed_shape = wxGetApp().plater()->bed_shape_bb();
|
||||||
|
new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast<double>(), -new_object->origin_translation(2)));
|
||||||
|
|
||||||
|
object_idxs.push_back(model.objects.size() - 1);
|
||||||
|
#ifdef _DEBUG
|
||||||
|
check_model_ids_validity(model);
|
||||||
|
#endif /* _DEBUG */
|
||||||
|
|
||||||
|
paste_objects_into_list(object_idxs);
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
check_model_ids_validity(model);
|
||||||
|
#endif /* _DEBUG */
|
||||||
|
}
|
||||||
|
|
||||||
void ObjectList::del_object(const int obj_idx)
|
void ObjectList::del_object(const int obj_idx)
|
||||||
{
|
{
|
||||||
wxGetApp().plater()->delete_object_from_model(obj_idx);
|
wxGetApp().plater()->delete_object_from_model(obj_idx);
|
||||||
|
@ -3582,7 +3679,8 @@ void ObjectList::msw_rescale()
|
||||||
&m_menu_part,
|
&m_menu_part,
|
||||||
&m_menu_sla_object,
|
&m_menu_sla_object,
|
||||||
&m_menu_instance,
|
&m_menu_instance,
|
||||||
&m_menu_layer })
|
&m_menu_layer,
|
||||||
|
&m_menu_default})
|
||||||
msw_rescale_menu(menu);
|
msw_rescale_menu(menu);
|
||||||
|
|
||||||
Layout();
|
Layout();
|
||||||
|
|
|
@ -132,6 +132,7 @@ private:
|
||||||
MenuWithSeparators m_menu_sla_object;
|
MenuWithSeparators m_menu_sla_object;
|
||||||
MenuWithSeparators m_menu_instance;
|
MenuWithSeparators m_menu_instance;
|
||||||
MenuWithSeparators m_menu_layer;
|
MenuWithSeparators m_menu_layer;
|
||||||
|
MenuWithSeparators m_menu_default;
|
||||||
wxMenuItem* m_menu_item_settings { nullptr };
|
wxMenuItem* m_menu_item_settings { nullptr };
|
||||||
wxMenuItem* m_menu_item_split_instances { nullptr };
|
wxMenuItem* m_menu_item_split_instances { nullptr };
|
||||||
|
|
||||||
|
@ -208,7 +209,7 @@ public:
|
||||||
void set_tooltip_for_item(const wxPoint& pt);
|
void set_tooltip_for_item(const wxPoint& pt);
|
||||||
|
|
||||||
void selection_changed();
|
void selection_changed();
|
||||||
void show_context_menu();
|
void show_context_menu(const bool evt_context_menu);
|
||||||
#ifndef __WXOSX__
|
#ifndef __WXOSX__
|
||||||
void key_event(wxKeyEvent& event);
|
void key_event(wxKeyEvent& event);
|
||||||
#endif /* __WXOSX__ */
|
#endif /* __WXOSX__ */
|
||||||
|
@ -240,6 +241,7 @@ public:
|
||||||
void create_sla_object_popupmenu(wxMenu*menu);
|
void create_sla_object_popupmenu(wxMenu*menu);
|
||||||
void create_part_popupmenu(wxMenu*menu);
|
void create_part_popupmenu(wxMenu*menu);
|
||||||
void create_instance_popupmenu(wxMenu*menu);
|
void create_instance_popupmenu(wxMenu*menu);
|
||||||
|
void create_default_popupmenu(wxMenu *menu);
|
||||||
wxMenu* create_settings_popupmenu(wxMenu *parent_menu);
|
wxMenu* create_settings_popupmenu(wxMenu *parent_menu);
|
||||||
void create_freq_settings_popupmenu(wxMenu *parent_menu, const bool is_object_settings = true);
|
void create_freq_settings_popupmenu(wxMenu *parent_menu, const bool is_object_settings = true);
|
||||||
|
|
||||||
|
@ -248,6 +250,7 @@ public:
|
||||||
void load_subobject(ModelVolumeType type);
|
void load_subobject(ModelVolumeType type);
|
||||||
void load_part(ModelObject* model_object, std::vector<std::pair<wxString, bool>> &volumes_info, ModelVolumeType type);
|
void load_part(ModelObject* model_object, std::vector<std::pair<wxString, bool>> &volumes_info, ModelVolumeType type);
|
||||||
void load_generic_subobject(const std::string& type_name, const ModelVolumeType type);
|
void load_generic_subobject(const std::string& type_name, const ModelVolumeType type);
|
||||||
|
void load_shape_object(const std::string &type_name);
|
||||||
void del_object(const int obj_idx);
|
void del_object(const int obj_idx);
|
||||||
void del_subobject_item(wxDataViewItem& item);
|
void del_subobject_item(wxDataViewItem& item);
|
||||||
void del_settings_from_config(const wxDataViewItem& parent_item);
|
void del_settings_from_config(const wxDataViewItem& parent_item);
|
||||||
|
@ -362,7 +365,7 @@ private:
|
||||||
// void OnChar(wxKeyEvent& event);
|
// void OnChar(wxKeyEvent& event);
|
||||||
#endif /* __WXOSX__ */
|
#endif /* __WXOSX__ */
|
||||||
void OnContextMenu(wxDataViewEvent &event);
|
void OnContextMenu(wxDataViewEvent &event);
|
||||||
void list_manipulation();
|
void list_manipulation(bool evt_context_menu = false);
|
||||||
|
|
||||||
void OnBeginDrag(wxDataViewEvent &event);
|
void OnBeginDrag(wxDataViewEvent &event);
|
||||||
void OnDropPossible(wxDataViewEvent &event);
|
void OnDropPossible(wxDataViewEvent &event);
|
||||||
|
|
|
@ -296,8 +296,9 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
|
||||||
// Matrices set, we can render the point mark now.
|
// Matrices set, we can render the point mark now.
|
||||||
// If in editing mode, we'll also render a cone pointing to the sphere.
|
// If in editing mode, we'll also render a cone pointing to the sphere.
|
||||||
if (m_editing_mode) {
|
if (m_editing_mode) {
|
||||||
|
// in case the normal is not yet cached, find and cache it
|
||||||
if (m_editing_cache[i].normal == Vec3f::Zero())
|
if (m_editing_cache[i].normal == Vec3f::Zero())
|
||||||
update_cache_entry_normal(i); // in case the normal is not yet cached, find and cache it
|
m_mesh_raycaster->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal);
|
||||||
|
|
||||||
Eigen::Quaterniond q;
|
Eigen::Quaterniond q;
|
||||||
q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast<double>());
|
q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast<double>());
|
||||||
|
@ -366,13 +367,8 @@ void GLGizmoSlaSupports::update_mesh()
|
||||||
m_its = &m_mesh->its;
|
m_its = &m_mesh->its;
|
||||||
|
|
||||||
// If this is different mesh than last time or if the AABB tree is uninitialized, recalculate it.
|
// If this is different mesh than last time or if the AABB tree is uninitialized, recalculate it.
|
||||||
if (m_model_object_id != m_model_object->id() || (m_AABB.m_left == NULL && m_AABB.m_right == NULL))
|
if (m_model_object_id != m_model_object->id() || ! m_mesh_raycaster)
|
||||||
{
|
m_mesh_raycaster.reset(new MeshRaycaster(*m_mesh));
|
||||||
m_AABB.deinit();
|
|
||||||
m_AABB.init(
|
|
||||||
MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3),
|
|
||||||
MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3));
|
|
||||||
}
|
|
||||||
|
|
||||||
m_model_object_id = m_model_object->id();
|
m_model_object_id = m_model_object->id();
|
||||||
disable_editing_mode();
|
disable_editing_mode();
|
||||||
|
@ -385,54 +381,25 @@ void GLGizmoSlaSupports::update_mesh()
|
||||||
bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal)
|
bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal)
|
||||||
{
|
{
|
||||||
// if the gizmo doesn't have the V, F structures for igl, calculate them first:
|
// if the gizmo doesn't have the V, F structures for igl, calculate them first:
|
||||||
if (m_its == nullptr)
|
if (! m_mesh_raycaster)
|
||||||
update_mesh();
|
update_mesh();
|
||||||
|
|
||||||
const Camera& camera = m_parent.get_camera();
|
const Camera& camera = m_parent.get_camera();
|
||||||
const std::array<int, 4>& viewport = camera.get_viewport();
|
|
||||||
const Transform3d& modelview_matrix = camera.get_view_matrix();
|
|
||||||
const Transform3d& projection_matrix = camera.get_projection_matrix();
|
|
||||||
|
|
||||||
Vec3d point1;
|
|
||||||
Vec3d point2;
|
|
||||||
::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 0.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point1(0), &point1(1), &point1(2));
|
|
||||||
::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 1.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point2(0), &point2(1), &point2(2));
|
|
||||||
|
|
||||||
std::vector<igl::Hit> hits;
|
|
||||||
|
|
||||||
const Selection& selection = m_parent.get_selection();
|
const Selection& selection = m_parent.get_selection();
|
||||||
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||||
|
Geometry::Transformation trafo = volume->get_instance_transformation();
|
||||||
|
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift));
|
||||||
|
|
||||||
point1(2) -= m_z_shift;
|
// The raycaster query
|
||||||
point2(2) -= m_z_shift;
|
std::vector<Vec3f> hits;
|
||||||
|
std::vector<Vec3f> normals;
|
||||||
|
m_mesh_raycaster->unproject_on_mesh(mouse_pos, trafo.get_matrix(), camera, &hits, &normals);
|
||||||
|
|
||||||
Transform3d inv = volume->get_instance_transformation().get_matrix().inverse();
|
// We must also take care of the clipping plane (if active)
|
||||||
|
unsigned i = 0;
|
||||||
point1 = inv * point1;
|
if (m_clipping_plane_distance != 0.f) {
|
||||||
point2 = inv * point2;
|
for (i=0; i<hits.size(); ++i)
|
||||||
|
if (! is_point_clipped(hits[i].cast<double>()))
|
||||||
if (!m_AABB.intersect_ray(
|
|
||||||
MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3),
|
|
||||||
MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3),
|
|
||||||
point1.cast<float>(), (point2-point1).cast<float>(), hits))
|
|
||||||
return false; // no intersection found
|
|
||||||
|
|
||||||
std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
|
|
||||||
|
|
||||||
// Now let's iterate through the points and find the first that is not clipped:
|
|
||||||
unsigned int i=0;
|
|
||||||
Vec3f bc;
|
|
||||||
Vec3f a;
|
|
||||||
Vec3f b;
|
|
||||||
Vec3f result;
|
|
||||||
for (i=0; i<hits.size(); ++i) {
|
|
||||||
igl::Hit& hit = hits[i];
|
|
||||||
int fid = hit.id; // facet id
|
|
||||||
bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit
|
|
||||||
a = (m_its->vertices[m_its->indices[fid](1)] - m_its->vertices[m_its->indices[fid](0)]);
|
|
||||||
b = (m_its->vertices[m_its->indices[fid](2)] - m_its->vertices[m_its->indices[fid](0)]);
|
|
||||||
result = bc(0) * m_its->vertices[m_its->indices[fid](0)] + bc(1) * m_its->vertices[m_its->indices[fid](1)] + bc(2)*m_its->vertices[m_its->indices[fid](2)];
|
|
||||||
if (m_clipping_plane_distance == 0.f || !is_point_clipped(result.cast<double>()))
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -443,7 +410,7 @@ bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate and return both the point and the facet normal.
|
// Calculate and return both the point and the facet normal.
|
||||||
pos_and_normal = std::make_pair(result, a.cross(b));
|
pos_and_normal = std::make_pair(hits[i], normals[i]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -504,76 +471,28 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||||
GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state();
|
GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state();
|
||||||
|
|
||||||
// First collect positions of all the points in world coordinates.
|
// First collect positions of all the points in world coordinates.
|
||||||
const Transform3d& instance_matrix = m_model_object->instances[m_active_instance]->get_transformation().get_matrix();
|
Geometry::Transformation trafo = m_model_object->instances[m_active_instance]->get_transformation();
|
||||||
|
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift));
|
||||||
std::vector<Vec3d> points;
|
std::vector<Vec3d> points;
|
||||||
for (unsigned int i=0; i<m_editing_cache.size(); ++i) {
|
for (unsigned int i=0; i<m_editing_cache.size(); ++i)
|
||||||
const sla::SupportPoint &support_point = m_editing_cache[i].support_point;
|
points.push_back(trafo.get_matrix() * m_editing_cache[i].support_point.pos.cast<double>());
|
||||||
points.push_back(instance_matrix * support_point.pos.cast<double>());
|
|
||||||
points.back()(2) += m_z_shift;
|
|
||||||
}
|
|
||||||
// Now ask the rectangle which of the points are inside.
|
// Now ask the rectangle which of the points are inside.
|
||||||
const Camera& camera = m_parent.get_camera();
|
std::vector<Vec3f> points_inside;
|
||||||
std::vector<unsigned int> selected_idxs = m_selection_rectangle.stop_dragging(m_parent, points);
|
std::vector<unsigned int> points_idxs = m_selection_rectangle.stop_dragging(m_parent, points);
|
||||||
|
for (size_t idx : points_idxs)
|
||||||
|
points_inside.push_back((trafo.get_matrix() * points[idx]).cast<float>());
|
||||||
|
|
||||||
// we'll recover current look direction (in world coords) and transform it to model coords.
|
// Only select/deselect points that are actually visible
|
||||||
const Selection& selection = m_parent.get_selection();
|
for (size_t idx : m_mesh_raycaster->get_unobscured_idxs(trafo, m_parent.get_camera(), points_inside,
|
||||||
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
[this](const Vec3f& pt) { return is_point_clipped(pt.cast<double>()); }))
|
||||||
const Transform3d& instance_matrix_no_translation_no_scaling = volume->get_instance_transformation().get_matrix(true,false,true);
|
{
|
||||||
Vec3f direction_to_camera = -camera.get_dir_forward().cast<float>();
|
const sla::SupportPoint &support_point = m_editing_cache[points_idxs[idx]].support_point;
|
||||||
Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast<float>() * direction_to_camera).normalized().eval();
|
|
||||||
Vec3f scaling = volume->get_instance_scaling_factor().cast<float>();
|
|
||||||
direction_to_camera_mesh = Vec3f(direction_to_camera_mesh(0)*scaling(0), direction_to_camera_mesh(1)*scaling(1), direction_to_camera_mesh(2)*scaling(2));
|
|
||||||
|
|
||||||
// Iterate over all points in the rectangle and check that they are neither clipped by the
|
|
||||||
// clipping plane nor obscured by the mesh.
|
|
||||||
for (const unsigned int i : selected_idxs) {
|
|
||||||
const sla::SupportPoint &support_point = m_editing_cache[i].support_point;
|
|
||||||
if (! is_point_clipped(support_point.pos.cast<double>())) {
|
if (! is_point_clipped(support_point.pos.cast<double>())) {
|
||||||
bool is_obscured = false;
|
|
||||||
// Cast a ray in the direction of the camera and look for intersection with the mesh:
|
|
||||||
std::vector<igl::Hit> hits;
|
|
||||||
// Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies.
|
|
||||||
if (m_AABB.intersect_ray(
|
|
||||||
MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3),
|
|
||||||
MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3),
|
|
||||||
support_point.pos + direction_to_camera_mesh * (support_point.head_front_radius + EPSILON), direction_to_camera_mesh, hits)) {
|
|
||||||
std::sort(hits.begin(), hits.end(), [](const igl::Hit& h1, const igl::Hit& h2) { return h1.t < h2.t; });
|
|
||||||
|
|
||||||
if (m_clipping_plane_distance != 0.f) {
|
|
||||||
// If the closest hit facet normal points in the same direction as the ray,
|
|
||||||
// we are looking through the mesh and should therefore discard the point:
|
|
||||||
int fid = hits.front().id; // facet id
|
|
||||||
Vec3f a = (m_its->vertices[m_its->indices[fid](1)] - m_its->vertices[m_its->indices[fid](0)]);
|
|
||||||
Vec3f b = (m_its->vertices[m_its->indices[fid](2)] - m_its->vertices[m_its->indices[fid](0)]);
|
|
||||||
if ((a.cross(b)).dot(direction_to_camera_mesh) > 0.f)
|
|
||||||
is_obscured = true;
|
|
||||||
|
|
||||||
// Eradicate all hits that are on clipped surfaces:
|
|
||||||
for (unsigned int j=0; j<hits.size(); ++j) {
|
|
||||||
const igl::Hit& hit = hits[j];
|
|
||||||
int fid = hit.id; // facet id
|
|
||||||
|
|
||||||
Vec3f bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit
|
|
||||||
Vec3f hit_pos = bc(0) * m_its->vertices[m_its->indices[fid](0)] + bc(1) * m_its->vertices[m_its->indices[fid](1)] + bc(2)*m_its->vertices[m_its->indices[fid](2)];
|
|
||||||
if (is_point_clipped(hit_pos.cast<double>())) {
|
|
||||||
hits.erase(hits.begin()+j);
|
|
||||||
--j;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction.
|
|
||||||
// Also, the threshold is in mesh coordinates, not in actual dimensions.
|
|
||||||
if (!hits.empty())
|
|
||||||
is_obscured = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_obscured) {
|
|
||||||
if (rectangle_status == GLSelectionRectangle::Deselect)
|
if (rectangle_status == GLSelectionRectangle::Deselect)
|
||||||
unselect_point(i);
|
unselect_point(points_idxs[idx]);
|
||||||
else
|
else
|
||||||
select_point(i);
|
select_point(points_idxs[idx]);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -731,23 +650,6 @@ std::vector<const ConfigOption*> GLGizmoSlaSupports::get_config_options(const st
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void GLGizmoSlaSupports::update_cache_entry_normal(size_t i) const
|
|
||||||
{
|
|
||||||
int idx = 0;
|
|
||||||
Eigen::Matrix<float, 1, 3> pp = m_editing_cache[i].support_point.pos;
|
|
||||||
Eigen::Matrix<float, 1, 3> cc;
|
|
||||||
m_AABB.squared_distance(
|
|
||||||
MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3),
|
|
||||||
MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3),
|
|
||||||
pp, idx, cc);
|
|
||||||
Vec3f a = (m_its->vertices[m_its->indices[idx](1)] - m_its->vertices[m_its->indices[idx](0)]);
|
|
||||||
Vec3f b = (m_its->vertices[m_its->indices[idx](2)] - m_its->vertices[m_its->indices[idx](0)]);
|
|
||||||
m_editing_cache[i].normal = a.cross(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ClippingPlane GLGizmoSlaSupports::get_sla_clipping_plane() const
|
ClippingPlane GLGizmoSlaSupports::get_sla_clipping_plane() const
|
||||||
{
|
{
|
||||||
if (!m_model_object || m_state == Off || m_clipping_plane_distance == 0.f)
|
if (!m_model_object || m_state == Off || m_clipping_plane_distance == 0.f)
|
||||||
|
@ -1100,11 +1002,11 @@ void GLGizmoSlaSupports::on_set_state()
|
||||||
m_parent.toggle_model_objects_visibility(true);
|
m_parent.toggle_model_objects_visibility(true);
|
||||||
m_normal_cache.clear();
|
m_normal_cache.clear();
|
||||||
m_clipping_plane_distance = 0.f;
|
m_clipping_plane_distance = 0.f;
|
||||||
// Release triangle mesh slicer and the AABB spatial search structure.
|
// Release clippers and the AABB raycaster.
|
||||||
m_AABB.deinit();
|
|
||||||
m_its = nullptr;
|
m_its = nullptr;
|
||||||
m_object_clipper.reset();
|
m_object_clipper.reset();
|
||||||
m_supports_clipper.reset();
|
m_supports_clipper.reset();
|
||||||
|
m_mesh_raycaster.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_old_state = m_state;
|
m_old_state = m_state;
|
||||||
|
|
|
@ -4,11 +4,6 @@
|
||||||
#include "GLGizmoBase.hpp"
|
#include "GLGizmoBase.hpp"
|
||||||
#include "slic3r/GUI/GLSelectionRectangle.hpp"
|
#include "slic3r/GUI/GLSelectionRectangle.hpp"
|
||||||
|
|
||||||
// There is an L function in igl that would be overridden by our localization macro - let's undefine it...
|
|
||||||
#undef L
|
|
||||||
#include <igl/AABB.h>
|
|
||||||
#include "slic3r/GUI/I18N.hpp" // ...and redefine again when we are done with the igl code
|
|
||||||
|
|
||||||
#include "libslic3r/SLA/SLACommon.hpp"
|
#include "libslic3r/SLA/SLACommon.hpp"
|
||||||
#include <wx/dialog.h>
|
#include <wx/dialog.h>
|
||||||
|
|
||||||
|
@ -20,6 +15,7 @@ namespace GUI {
|
||||||
|
|
||||||
class ClippingPlane;
|
class ClippingPlane;
|
||||||
class MeshClipper;
|
class MeshClipper;
|
||||||
|
class MeshRaycaster;
|
||||||
enum class SLAGizmoEventType : unsigned char;
|
enum class SLAGizmoEventType : unsigned char;
|
||||||
|
|
||||||
class GLGizmoSlaSupports : public GLGizmoBase
|
class GLGizmoSlaSupports : public GLGizmoBase
|
||||||
|
@ -37,7 +33,8 @@ private:
|
||||||
GLUquadricObj* m_quadric;
|
GLUquadricObj* m_quadric;
|
||||||
typedef Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned;
|
typedef Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned;
|
||||||
typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned;
|
typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned;
|
||||||
igl::AABB<MapMatrixXfUnaligned, 3> m_AABB;
|
|
||||||
|
std::unique_ptr<MeshRaycaster> m_mesh_raycaster;
|
||||||
const TriangleMesh* m_mesh;
|
const TriangleMesh* m_mesh;
|
||||||
const indexed_triangle_set* m_its;
|
const indexed_triangle_set* m_its;
|
||||||
mutable const TriangleMesh* m_supports_mesh;
|
mutable const TriangleMesh* m_supports_mesh;
|
||||||
|
@ -98,7 +95,6 @@ private:
|
||||||
void render_clipping_plane(const Selection& selection) const;
|
void render_clipping_plane(const Selection& selection) const;
|
||||||
bool is_mesh_update_necessary() const;
|
bool is_mesh_update_necessary() const;
|
||||||
void update_mesh();
|
void update_mesh();
|
||||||
void update_cache_entry_normal(size_t i) const;
|
|
||||||
bool unsaved_changes() const;
|
bool unsaved_changes() const;
|
||||||
|
|
||||||
bool m_lock_unique_islands = false;
|
bool m_lock_unique_islands = false;
|
||||||
|
|
|
@ -3,6 +3,15 @@
|
||||||
#include "libslic3r/Tesselate.hpp"
|
#include "libslic3r/Tesselate.hpp"
|
||||||
#include "libslic3r/TriangleMesh.hpp"
|
#include "libslic3r/TriangleMesh.hpp"
|
||||||
|
|
||||||
|
#include "slic3r/GUI/Camera.hpp"
|
||||||
|
|
||||||
|
// There is an L function in igl that would be overridden by our localization macro.
|
||||||
|
#undef L
|
||||||
|
#include <igl/AABB.h>
|
||||||
|
|
||||||
|
#include <GL/glew.h>
|
||||||
|
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace GUI {
|
namespace GUI {
|
||||||
|
|
||||||
|
@ -90,6 +99,168 @@ void MeshClipper::recalculate_triangles()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MeshRaycaster::AABBWrapper {
|
||||||
|
public:
|
||||||
|
AABBWrapper(const TriangleMesh* mesh);
|
||||||
|
~AABBWrapper() { m_AABB.deinit(); }
|
||||||
|
|
||||||
|
typedef Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned;
|
||||||
|
typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned;
|
||||||
|
igl::AABB<MapMatrixXfUnaligned, 3> m_AABB;
|
||||||
|
|
||||||
|
Vec3f get_hit_pos(const igl::Hit& hit) const;
|
||||||
|
Vec3f get_hit_normal(const igl::Hit& hit) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const TriangleMesh* m_mesh;
|
||||||
|
};
|
||||||
|
|
||||||
|
MeshRaycaster::AABBWrapper::AABBWrapper(const TriangleMesh* mesh)
|
||||||
|
: m_mesh(mesh)
|
||||||
|
{
|
||||||
|
m_AABB.init(
|
||||||
|
MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3),
|
||||||
|
MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MeshRaycaster::MeshRaycaster(const TriangleMesh& mesh)
|
||||||
|
: m_AABB_wrapper(new AABBWrapper(&mesh)), m_mesh(&mesh)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MeshRaycaster::~MeshRaycaster()
|
||||||
|
{
|
||||||
|
delete m_AABB_wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3f MeshRaycaster::AABBWrapper::get_hit_pos(const igl::Hit& hit) const
|
||||||
|
{
|
||||||
|
const stl_triangle_vertex_indices& indices = m_mesh->its.indices[hit.id];
|
||||||
|
return Vec3f((1-hit.u-hit.v) * m_mesh->its.vertices[indices(0)]
|
||||||
|
+ hit.u * m_mesh->its.vertices[indices(1)]
|
||||||
|
+ hit.v * m_mesh->its.vertices[indices(2)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Vec3f MeshRaycaster::AABBWrapper::get_hit_normal(const igl::Hit& hit) const
|
||||||
|
{
|
||||||
|
const stl_triangle_vertex_indices& indices = m_mesh->its.indices[hit.id];
|
||||||
|
Vec3f a(m_mesh->its.vertices[indices(1)] - m_mesh->its.vertices[indices(0)]);
|
||||||
|
Vec3f b(m_mesh->its.vertices[indices(2)] - m_mesh->its.vertices[indices(0)]);
|
||||||
|
return Vec3f(a.cross(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo,
|
||||||
|
const Camera& camera, std::vector<Vec3f>* positions, std::vector<Vec3f>* normals) const
|
||||||
|
{
|
||||||
|
const std::array<int, 4>& viewport = camera.get_viewport();
|
||||||
|
const Transform3d& model_mat = camera.get_view_matrix();
|
||||||
|
const Transform3d& proj_mat = camera.get_projection_matrix();
|
||||||
|
|
||||||
|
Vec3d pt1;
|
||||||
|
Vec3d pt2;
|
||||||
|
::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 0., model_mat.data(), proj_mat.data(), viewport.data(), &pt1(0), &pt1(1), &pt1(2));
|
||||||
|
::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 1., model_mat.data(), proj_mat.data(), viewport.data(), &pt2(0), &pt2(1), &pt2(2));
|
||||||
|
|
||||||
|
std::vector<igl::Hit> hits;
|
||||||
|
|
||||||
|
Transform3d inv = trafo.inverse();
|
||||||
|
|
||||||
|
pt1 = inv * pt1;
|
||||||
|
pt2 = inv * pt2;
|
||||||
|
|
||||||
|
if (! m_AABB_wrapper->m_AABB.intersect_ray(
|
||||||
|
AABBWrapper::MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3),
|
||||||
|
AABBWrapper::MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3),
|
||||||
|
pt1.cast<float>(), (pt2-pt1).cast<float>(), hits))
|
||||||
|
return false; // no intersection found
|
||||||
|
|
||||||
|
std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
|
||||||
|
|
||||||
|
// Now stuff the points in the provided vector and calculate normals if asked about them:
|
||||||
|
if (positions != nullptr) {
|
||||||
|
positions->clear();
|
||||||
|
if (normals != nullptr)
|
||||||
|
normals->clear();
|
||||||
|
for (const igl::Hit& hit : hits) {
|
||||||
|
positions->push_back(m_AABB_wrapper->get_hit_pos(hit));
|
||||||
|
|
||||||
|
if (normals != nullptr)
|
||||||
|
normals->push_back(m_AABB_wrapper->get_hit_normal(hit));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, const std::vector<Vec3f>& points,
|
||||||
|
std::function<bool(const Vec3f&)> fn_ignore_hit) const
|
||||||
|
{
|
||||||
|
std::vector<unsigned> out;
|
||||||
|
|
||||||
|
const Transform3d& instance_matrix_no_translation_no_scaling = trafo.get_matrix(true,false,true);
|
||||||
|
Vec3f direction_to_camera = -camera.get_dir_forward().cast<float>();
|
||||||
|
Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast<float>() * direction_to_camera).normalized().eval();
|
||||||
|
Vec3f scaling = trafo.get_scaling_factor().cast<float>();
|
||||||
|
direction_to_camera_mesh = Vec3f(direction_to_camera_mesh(0)*scaling(0), direction_to_camera_mesh(1)*scaling(1), direction_to_camera_mesh(2)*scaling(2));
|
||||||
|
|
||||||
|
for (size_t i=0; i<points.size(); ++i) {
|
||||||
|
const Vec3f& pt = points[i];
|
||||||
|
bool is_obscured = false;
|
||||||
|
// Cast a ray in the direction of the camera and look for intersection with the mesh:
|
||||||
|
std::vector<igl::Hit> hits;
|
||||||
|
// Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies.
|
||||||
|
if (m_AABB_wrapper->m_AABB.intersect_ray(
|
||||||
|
AABBWrapper::MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3),
|
||||||
|
AABBWrapper::MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3),
|
||||||
|
pt + direction_to_camera_mesh * EPSILON, direction_to_camera_mesh, hits)) {
|
||||||
|
|
||||||
|
std::sort(hits.begin(), hits.end(), [](const igl::Hit& h1, const igl::Hit& h2) { return h1.t < h2.t; });
|
||||||
|
// If the closest hit facet normal points in the same direction as the ray,
|
||||||
|
// we are looking through the mesh and should therefore discard the point:
|
||||||
|
if (m_AABB_wrapper->get_hit_normal(hits.front()).dot(direction_to_camera_mesh) > 0.f)
|
||||||
|
is_obscured = true;
|
||||||
|
|
||||||
|
// Eradicate all hits that the caller wants to ignore
|
||||||
|
for (unsigned j=0; j<hits.size(); ++j) {
|
||||||
|
const igl::Hit& hit = hits[j];
|
||||||
|
if (fn_ignore_hit(m_AABB_wrapper->get_hit_pos(hit))) {
|
||||||
|
hits.erase(hits.begin()+j);
|
||||||
|
--j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction.
|
||||||
|
// Also, the threshold is in mesh coordinates, not in actual dimensions.
|
||||||
|
if (! hits.empty())
|
||||||
|
is_obscured = true;
|
||||||
|
}
|
||||||
|
if (! is_obscured)
|
||||||
|
out.push_back(i);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const
|
||||||
|
{
|
||||||
|
int idx = 0;
|
||||||
|
Eigen::Matrix<float, 1, 3> closest_point;
|
||||||
|
m_AABB_wrapper->m_AABB.squared_distance(
|
||||||
|
AABBWrapper::MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3),
|
||||||
|
AABBWrapper::MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3),
|
||||||
|
point, idx, closest_point);
|
||||||
|
if (normal) {
|
||||||
|
igl::Hit imag_hit;
|
||||||
|
imag_hit.id = idx;
|
||||||
|
*normal = m_AABB_wrapper->get_hit_normal(imag_hit);
|
||||||
|
}
|
||||||
|
return closest_point;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace GUI
|
} // namespace GUI
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
|
@ -14,6 +14,8 @@ class TriangleMeshSlicer;
|
||||||
|
|
||||||
namespace GUI {
|
namespace GUI {
|
||||||
|
|
||||||
|
struct Camera;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ClippingPlane
|
class ClippingPlane
|
||||||
|
@ -87,6 +89,30 @@ private:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MeshRaycaster {
|
||||||
|
public:
|
||||||
|
MeshRaycaster(const TriangleMesh& mesh);
|
||||||
|
~MeshRaycaster();
|
||||||
|
void set_transformation(const Geometry::Transformation& trafo);
|
||||||
|
void set_camera(const Camera& camera);
|
||||||
|
|
||||||
|
bool unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
|
||||||
|
std::vector<Vec3f>* positions = nullptr, std::vector<Vec3f>* normals = nullptr) const;
|
||||||
|
|
||||||
|
std::vector<unsigned> get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera,
|
||||||
|
const std::vector<Vec3f>& points, std::function<bool(const Vec3f&)> fn_ignore_hit) const;
|
||||||
|
|
||||||
|
Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// PIMPL wrapper around igl::AABB so I don't have to include the header-only IGL here
|
||||||
|
class AABBWrapper;
|
||||||
|
AABBWrapper* m_AABB_wrapper;
|
||||||
|
const TriangleMesh* m_mesh = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace GUI
|
} // namespace GUI
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
|
|
@ -522,7 +522,11 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) :
|
||||||
const std::vector<double> &init_extruders = (project_config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values;
|
const std::vector<double> &init_extruders = (project_config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values;
|
||||||
|
|
||||||
const DynamicPrintConfig* config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
const DynamicPrintConfig* config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||||
const std::vector<std::string> &extruder_colours = (config->option<ConfigOptionStrings>("extruder_colour"))->values;
|
std::vector<std::string> extruder_colours = (config->option<ConfigOptionStrings>("extruder_colour"))->values;
|
||||||
|
const std::vector<std::string>& filament_colours = (wxGetApp().plater()->get_plater_config()->option<ConfigOptionStrings>("filament_colour"))->values;
|
||||||
|
for (size_t i=0; i<extruder_colours.size(); ++i)
|
||||||
|
if (extruder_colours[i] == "" && i < filament_colours.size())
|
||||||
|
extruder_colours[i] = filament_colours[i];
|
||||||
|
|
||||||
WipingDialog dlg(parent, cast<float>(init_matrix), cast<float>(init_extruders), extruder_colours);
|
WipingDialog dlg(parent, cast<float>(init_matrix), cast<float>(init_extruders), extruder_colours);
|
||||||
|
|
||||||
|
@ -2514,6 +2518,10 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type)
|
||||||
if (output_file.empty())
|
if (output_file.empty())
|
||||||
// Find the file name of the first printable object.
|
// Find the file name of the first printable object.
|
||||||
output_file = this->model.propose_export_file_name_and_path();
|
output_file = this->model.propose_export_file_name_and_path();
|
||||||
|
|
||||||
|
if (output_file.empty() && !model.objects.empty())
|
||||||
|
// Find the file name of the first object.
|
||||||
|
output_file = this->model.objects[0]->get_export_filename();
|
||||||
}
|
}
|
||||||
|
|
||||||
wxString dlg_title;
|
wxString dlg_title;
|
||||||
|
@ -4838,6 +4846,11 @@ void Plater::on_activate()
|
||||||
this->p->show_delayed_error_message();
|
this->p->show_delayed_error_message();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DynamicPrintConfig* Plater::get_plater_config() const
|
||||||
|
{
|
||||||
|
return p->config;
|
||||||
|
}
|
||||||
|
|
||||||
wxString Plater::get_project_filename(const wxString& extension) const
|
wxString Plater::get_project_filename(const wxString& extension) const
|
||||||
{
|
{
|
||||||
return p->get_project_filename(extension);
|
return p->get_project_filename(extension);
|
||||||
|
@ -4868,6 +4881,11 @@ GLCanvas3D* Plater::canvas3D()
|
||||||
return p->view3D->get_canvas3d();
|
return p->view3D->get_canvas3d();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BoundingBoxf Plater::bed_shape_bb() const
|
||||||
|
{
|
||||||
|
return p->bed_shape_bb();
|
||||||
|
}
|
||||||
|
|
||||||
PrinterTechnology Plater::printer_technology() const
|
PrinterTechnology Plater::printer_technology() const
|
||||||
{
|
{
|
||||||
return p->printer_technology;
|
return p->printer_technology;
|
||||||
|
|
|
@ -213,6 +213,7 @@ public:
|
||||||
void on_config_change(const DynamicPrintConfig &config);
|
void on_config_change(const DynamicPrintConfig &config);
|
||||||
// On activating the parent window.
|
// On activating the parent window.
|
||||||
void on_activate();
|
void on_activate();
|
||||||
|
const DynamicPrintConfig* get_plater_config() const;
|
||||||
|
|
||||||
void update_object_menu();
|
void update_object_menu();
|
||||||
|
|
||||||
|
@ -224,6 +225,7 @@ public:
|
||||||
int get_selected_object_idx();
|
int get_selected_object_idx();
|
||||||
bool is_single_full_object_selection() const;
|
bool is_single_full_object_selection() const;
|
||||||
GLCanvas3D* canvas3D();
|
GLCanvas3D* canvas3D();
|
||||||
|
BoundingBoxf bed_shape_bb() const;
|
||||||
|
|
||||||
PrinterTechnology printer_technology() const;
|
PrinterTechnology printer_technology() const;
|
||||||
void set_printer_technology(PrinterTechnology printer_technology);
|
void set_printer_technology(PrinterTechnology printer_technology);
|
||||||
|
|
|
@ -31,13 +31,11 @@
|
||||||
ExtrusionEntityCollection* flatten()
|
ExtrusionEntityCollection* flatten()
|
||||||
%code{%
|
%code{%
|
||||||
RETVAL = new ExtrusionEntityCollection();
|
RETVAL = new ExtrusionEntityCollection();
|
||||||
THIS->flatten(RETVAL);
|
*RETVAL = THIS->flatten();
|
||||||
%};
|
%};
|
||||||
double min_mm3_per_mm();
|
double min_mm3_per_mm();
|
||||||
bool empty()
|
bool empty()
|
||||||
%code{% RETVAL = THIS->entities.empty(); %};
|
%code{% RETVAL = THIS->entities.empty(); %};
|
||||||
std::vector<size_t> orig_indices()
|
|
||||||
%code{% RETVAL = THIS->orig_indices; %};
|
|
||||||
Polygons polygons_covered_by_width();
|
Polygons polygons_covered_by_width();
|
||||||
Polygons polygons_covered_by_spacing();
|
Polygons polygons_covered_by_spacing();
|
||||||
%{
|
%{
|
||||||
|
|
Loading…
Reference in a new issue